topsecrettraders commited on
Commit
ca5eacb
·
verified ·
1 Parent(s): d7274f6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +129 -27
app.py CHANGED
@@ -367,8 +367,11 @@ HTML_TEMPLATE = """
367
 
368
  /* Aesthetics: Big Number, No BG, Small label below */
369
  .aggr-val { font-size: 20px; font-family: 'JetBrains Mono'; font-weight: 800; line-height: 1; margin-bottom: 2px; }
370
- .aggr-lbl { font-size: 9px; font-weight: 700; color: #aaa; background: transparent; padding: 0; border-radius: 0; margin-top: 2px; }
371
 
 
 
 
372
  /* Modals */
373
  .modal { position: fixed; top:0; left:0; width:100%; height:100%; background: rgba(0,0,0,0.5); z-index: 1000; display: none; justify-content: center; align-items: center; backdrop-filter: blur(2px); }
374
  .modal-box { background: #fff; width: 420px; border-radius: 12px; box-shadow: 0 20px 50px rgba(0,0,0,0.2); padding: 25px; display:flex; flex-direction:column; animation: popIn 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); }
@@ -385,6 +388,12 @@ HTML_TEMPLATE = """
385
  .modal-action { background:var(--accent); color:white; border:none; padding:10px 20px; border-radius:6px; font-weight:bold; cursor:pointer; width:100%; margin-top:20px; }
386
  .modal-action.sec { background:#eee; color:#333; margin-top:10px; }
387
 
 
 
 
 
 
 
388
  .preset-pill { display:inline-block; padding:4px 8px; background:#f0f0f0; border-radius:4px; font-size:10px; font-weight:700; margin-right:5px; cursor:pointer; color:#555; border:1px solid transparent; }
389
  .preset-pill:hover { border-color:#aaa; background:#e0e0e0; }
390
 
@@ -439,6 +448,26 @@ HTML_TEMPLATE = """
439
  </div>
440
  </div>
441
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
442
  <!-- CONFIG MODAL (Left/Right Columns) -->
443
  <div class="modal" id="confModal">
444
  <div class="modal-box">
@@ -584,6 +613,9 @@ HTML_TEMPLATE = """
584
  let IS_POLLING = false;
585
  let SEARCH_TIMER = null;
586
  let ATM_STRIKE = 0;
 
 
 
587
 
588
  // Default Configurations
589
  const DEFAULTS = {
@@ -640,11 +672,13 @@ HTML_TEMPLATE = """
640
  }
641
 
642
  con.innerHTML = CONFIG.side.map((item, idx) => {
643
- // Create grid boxes for ranges. Flex handles the 50/50, 33/33 automatically.
644
  const boxes = item.ranges.map((r, rIdx) => `
645
  <div class="aggr-item">
646
  <span class="aggr-val" id="sv-${idx}-${rIdx}">0</span>
647
- <span class="aggr-lbl">ATM ±${r}</span>
 
 
 
648
  </div>
649
  `).join('');
650
 
@@ -823,6 +857,68 @@ HTML_TEMPLATE = """
823
  return 0;
824
  }
825
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
826
 
827
  // ===================================
828
  // APP LOGIC
@@ -1111,6 +1207,9 @@ HTML_TEMPLATE = """
1111
  rowData[item.k].PV = d.v;
1112
  }
1113
  }
 
 
 
1114
 
1115
  // --- RENDER CHAIN (Main Columns) ---
1116
  let maxL = 0, maxR = 0;
@@ -1170,35 +1269,38 @@ HTML_TEMPLATE = """
1170
  }
1171
  }
1172
 
1173
- // --- SIDEBAR DYNAMIC AGGREGATION ---
1174
- if(ATM_STRIKE > 0 && CONFIG.side && CONFIG.side.length > 0) {
1175
  const kList = Object.keys(rowData).map(parseFloat).sort((a,b)=>a-b);
1176
- const atmIdx = kList.indexOf(ATM_STRIKE);
1177
 
1178
- if(atmIdx !== -1) {
1179
- // Loop through each configured Card
1180
- CONFIG.side.forEach((item, idx) => {
1181
- // Loop through each Range in the card
1182
- item.ranges.forEach((rng, rIdx) => {
1183
- const el = document.getElementById(`sv-${idx}-${rIdx}`);
1184
- if(el) {
1185
- let total = 0;
1186
- const start = Math.max(0, atmIdx - rng);
1187
- const end = Math.min(kList.length, atmIdx + rng + 1);
1188
-
1189
- for(let i=start; i<end; i++) {
1190
- const k = kList[i];
1191
- if(rowData[k]) {
1192
- total += evaluateFormula(item.formula, rowData[k]);
1193
- }
 
 
 
 
1194
  }
1195
-
1196
- el.innerText = (total > 0 ? "+" : "") + fmt(total);
1197
- el.style.color = total >= 0 ? 'var(--green)' : 'var(--red)';
1198
  }
1199
- });
 
 
 
1200
  });
1201
- }
1202
  }
1203
  }
1204
 
 
367
 
368
  /* Aesthetics: Big Number, No BG, Small label below */
369
  .aggr-val { font-size: 20px; font-family: 'JetBrains Mono'; font-weight: 800; line-height: 1; margin-bottom: 2px; }
370
+ .aggr-lbl { font-size: 9px; font-weight: 700; color: #aaa; background: transparent; padding: 0; border-radius: 0; margin-top: 2px; display:flex; align-items:center; gap:3px; }
371
 
372
+ .info-bubble { width:12px; height:12px; background:#eee; color:#666; border-radius:50%; font-size:8px; display:inline-flex; align-items:center; justify-content:center; cursor:pointer; font-family:'Inter'; font-weight:800; }
373
+ .info-bubble:hover { background:var(--accent); color:white; }
374
+
375
  /* Modals */
376
  .modal { position: fixed; top:0; left:0; width:100%; height:100%; background: rgba(0,0,0,0.5); z-index: 1000; display: none; justify-content: center; align-items: center; backdrop-filter: blur(2px); }
377
  .modal-box { background: #fff; width: 420px; border-radius: 12px; box-shadow: 0 20px 50px rgba(0,0,0,0.2); padding: 25px; display:flex; flex-direction:column; animation: popIn 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); }
 
388
  .modal-action { background:var(--accent); color:white; border:none; padding:10px 20px; border-radius:6px; font-weight:bold; cursor:pointer; width:100%; margin-top:20px; }
389
  .modal-action.sec { background:#eee; color:#333; margin-top:10px; }
390
 
391
+ /* INFO TABLE */
392
+ .info-table { width:100%; font-family:'JetBrains Mono'; font-size:10px; border-collapse: collapse; margin-top:10px; }
393
+ .info-table th { text-align:left; color:#888; border-bottom:1px solid #eee; padding:5px; }
394
+ .info-table td { padding:5px; border-bottom:1px solid #f9f9f9; color:#333; }
395
+ .info-row-res { background: #f0f7ff; font-weight:800; }
396
+
397
  .preset-pill { display:inline-block; padding:4px 8px; background:#f0f0f0; border-radius:4px; font-size:10px; font-weight:700; margin-right:5px; cursor:pointer; color:#555; border:1px solid transparent; }
398
  .preset-pill:hover { border-color:#aaa; background:#e0e0e0; }
399
 
 
448
  </div>
449
  </div>
450
 
451
+ <!-- INFO BREAKDOWN MODAL -->
452
+ <div class="modal" id="infoModal">
453
+ <div class="modal-box" style="width: 500px; max-width:95%;">
454
+ <div style="display:flex; justify-content:space-between; font-weight:800; font-size:16px; border-bottom:1px solid #eee; padding-bottom:15px;">
455
+ <span>CALCULATION BREAKDOWN</span>
456
+ <span style="cursor:pointer; color:#999;" onclick="closeModal('infoModal')">✕</span>
457
+ </div>
458
+ <div style="margin-top:15px; font-size:12px;">
459
+ <div><strong>LIVE SPOT:</strong> <span id="infoSpot" style="font-family:'JetBrains Mono'; color:var(--accent);">0.00</span></div>
460
+ <div style="margin-top:5px;"><strong>FORMULA:</strong> <span id="infoForm" style="font-family:'JetBrains Mono'; background:#f4f4f4; padding:2px 6px; border-radius:4px;"></span></div>
461
+ </div>
462
+ <div style="overflow-y:auto; max-height:400px; margin-top:10px;">
463
+ <table class="info-table" id="infoTable">
464
+ <!-- Dynamic Content -->
465
+ </table>
466
+ </div>
467
+ <div style="margin-top:10px; font-size:10px; color:#888; text-align:center;">* Uses Raw Integer Values for Accuracy</div>
468
+ </div>
469
+ </div>
470
+
471
  <!-- CONFIG MODAL (Left/Right Columns) -->
472
  <div class="modal" id="confModal">
473
  <div class="modal-box">
 
613
  let IS_POLLING = false;
614
  let SEARCH_TIMER = null;
615
  let ATM_STRIKE = 0;
616
+
617
+ // DATA STORE FOR INFO MODAL
618
+ let LATEST_ROW_DATA = {};
619
 
620
  // Default Configurations
621
  const DEFAULTS = {
 
672
  }
673
 
674
  con.innerHTML = CONFIG.side.map((item, idx) => {
 
675
  const boxes = item.ranges.map((r, rIdx) => `
676
  <div class="aggr-item">
677
  <span class="aggr-val" id="sv-${idx}-${rIdx}">0</span>
678
+ <span class="aggr-lbl">
679
+ ATM ±${r}
680
+ <span class="info-bubble" onclick="showInfo(${idx}, ${rIdx})">i</span>
681
+ </span>
682
  </div>
683
  `).join('');
684
 
 
857
  return 0;
858
  }
859
  }
860
+
861
+ // ===================================
862
+ // INFO MODAL LOGIC
863
+ // ===================================
864
+ function showInfo(cardIdx, rangeIdx) {
865
+ if(!CONFIG.side[cardIdx]) return;
866
+
867
+ const cfg = CONFIG.side[cardIdx];
868
+ const range = cfg.ranges[rangeIdx];
869
+ const formula = cfg.formula;
870
+
871
+ document.getElementById('infoSpot').innerText = SPOT_PRICE.toFixed(2);
872
+ document.getElementById('infoForm').innerText = formula;
873
+
874
+ // RE-RUN SPLIT LOGIC
875
+ const kList = Object.keys(LATEST_ROW_DATA).map(parseFloat).sort((a,b)=>a-b);
876
+ const lowers = kList.filter(k => k < SPOT_PRICE);
877
+ const uppers = kList.filter(k => k >= SPOT_PRICE); // Exact match goes to upper/call side
878
+
879
+ const selLow = lowers.slice(-range);
880
+ const selHigh = uppers.slice(0, range);
881
+
882
+ const allSel = [...selLow, ...selHigh].sort((a,b)=>a-b);
883
+ let grandTotal = 0;
884
+
885
+ const table = document.getElementById('infoTable');
886
+
887
+ // Headers based on formula vars? No, just show standard vars
888
+ let hHTML = `<tr>
889
+ <th style="width:50px;">STRIKE</th>
890
+ <th>CS</th><th>CB</th><th>PS</th><th>PB</th><th>CALC</th>
891
+ </tr>`;
892
+
893
+ let rowsHTML = allSel.map(k => {
894
+ const d = LATEST_ROW_DATA[k] || {};
895
+ const val = evaluateFormula(formula, d);
896
+ grandTotal += val;
897
+
898
+ // Styling for spot split
899
+ const borderStyle = (k >= SPOT_PRICE && selLow.includes(k- (kList[1]-kList[0]))) ? "border-top:2px solid var(--accent);" : "";
900
+
901
+ return `
902
+ <tr style="${borderStyle}">
903
+ <td style="font-weight:bold;">${k}</td>
904
+ <td>${d.CS || 0}</td>
905
+ <td>${d.CB || 0}</td>
906
+ <td>${d.PS || 0}</td>
907
+ <td>${d.PB || 0}</td>
908
+ <td style="font-weight:bold; color:${val>=0?'var(--green)':'var(--red)'}">${val}</td>
909
+ </tr>`;
910
+ }).join('');
911
+
912
+ // Footer
913
+ rowsHTML += `<tr class="info-row-res">
914
+ <td colspan="5" style="text-align:right;">TOTAL</td>
915
+ <td style="color:${grandTotal>=0?'var(--green)':'var(--red)'}">${grandTotal}</td>
916
+ </tr>`;
917
+
918
+ table.innerHTML = hHTML + rowsHTML;
919
+
920
+ document.getElementById('infoModal').style.display = 'flex';
921
+ }
922
 
923
  // ===================================
924
  // APP LOGIC
 
1207
  rowData[item.k].PV = d.v;
1208
  }
1209
  }
1210
+
1211
+ // STORE GLOBAL FOR INFO MODAL
1212
+ LATEST_ROW_DATA = rowData;
1213
 
1214
  // --- RENDER CHAIN (Main Columns) ---
1215
  let maxL = 0, maxR = 0;
 
1269
  }
1270
  }
1271
 
1272
+ // --- SIDEBAR DYNAMIC AGGREGATION (SYMMETRIC OTM LOGIC) ---
1273
+ if(SPOT_PRICE > 0 && CONFIG.side && CONFIG.side.length > 0) {
1274
  const kList = Object.keys(rowData).map(parseFloat).sort((a,b)=>a-b);
 
1275
 
1276
+ // SPLIT LIST BASED ON EXACT SPOT PRICE
1277
+ const lowers = kList.filter(k => k < SPOT_PRICE);
1278
+ const uppers = kList.filter(k => k >= SPOT_PRICE); // Include exact match in upper/call bucket
1279
+
1280
+ CONFIG.side.forEach((item, idx) => {
1281
+ item.ranges.forEach((rng, rIdx) => {
1282
+ const el = document.getElementById(`sv-${idx}-${rIdx}`);
1283
+ if(el) {
1284
+ // Take 'rng' amount from bottom of lowers (Closest below spot)
1285
+ const selLow = lowers.slice(-rng);
1286
+ // Take 'rng' amount from top of uppers (Closest above spot)
1287
+ const selHigh = uppers.slice(0, rng);
1288
+
1289
+ // Combine logic
1290
+ const targetStrikes = [...selLow, ...selHigh];
1291
+
1292
+ let total = 0;
1293
+ for(const k of targetStrikes) {
1294
+ if(rowData[k]) {
1295
+ total += evaluateFormula(item.formula, rowData[k]);
1296
  }
 
 
 
1297
  }
1298
+
1299
+ el.innerText = (total > 0 ? "+" : "") + fmt(total);
1300
+ el.style.color = total >= 0 ? 'var(--green)' : 'var(--red)';
1301
+ }
1302
  });
1303
+ });
1304
  }
1305
  }
1306