Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -501,6 +501,11 @@ HTML_TEMPLATE = """
|
|
| 501 |
.sec-pill { padding: 2px 5px; font-size: 10px; cursor: pointer; border-radius: 2px; color: #666; font-family:'JetBrains Mono'; }
|
| 502 |
.sec-pill:hover { background: #ddd; color: #000; }
|
| 503 |
.sec-pill.active { background: var(--accent); color: white; font-weight: bold; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 504 |
|
| 505 |
.grid-layout { display: flex; height: calc(100vh - 60px); }
|
| 506 |
|
|
@@ -932,15 +937,93 @@ HTML_TEMPLATE = """
|
|
| 932 |
document.getElementById('loader').style.display = 'none';
|
| 933 |
}
|
| 934 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 935 |
function renderSecondBar() {
|
| 936 |
const bar = document.getElementById('secBar');
|
| 937 |
-
bar.innerHTML = CACHED_TIMELINE.map((item, idx) =>
|
| 938 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 939 |
onclick="selectSnapshot(${idx})"
|
| 940 |
-
id="pill-${idx}"
|
|
|
|
| 941 |
:${item.s}
|
| 942 |
</div>
|
| 943 |
-
`).join('');
|
| 944 |
}
|
| 945 |
|
| 946 |
function selectSnapshot(idx) {
|
|
|
|
| 501 |
.sec-pill { padding: 2px 5px; font-size: 10px; cursor: pointer; border-radius: 2px; color: #666; font-family:'JetBrains Mono'; }
|
| 502 |
.sec-pill:hover { background: #ddd; color: #000; }
|
| 503 |
.sec-pill.active { background: var(--accent); color: white; font-weight: bold; }
|
| 504 |
+
|
| 505 |
+
/* CORRUPTED DATA INDICATOR */
|
| 506 |
+
.sec-pill.bad { background: #ff5252; color: white; opacity: 0.9; }
|
| 507 |
+
.sec-pill.bad:hover { background: #d50000; }
|
| 508 |
+
.sec-pill.bad.active { background: #b71c1c; border: 1px solid #fff; }
|
| 509 |
|
| 510 |
.grid-layout { display: flex; height: calc(100vh - 60px); }
|
| 511 |
|
|
|
|
| 937 |
document.getElementById('loader').style.display = 'none';
|
| 938 |
}
|
| 939 |
|
| 940 |
+
// --- VALIDATION LOGIC FOR HISTORICAL DATA ---
|
| 941 |
+
function validateSnapshot(data) {
|
| 942 |
+
// 1. Identify Reference Price (Spot or Future)
|
| 943 |
+
let refPrice = 0;
|
| 944 |
+
let hasOptions = false;
|
| 945 |
+
|
| 946 |
+
// Loop to find Reference and check if data exists
|
| 947 |
+
for(let sym in data) {
|
| 948 |
+
// Check for Spot or Index (Priority)
|
| 949 |
+
if(sym.includes('INDEX') || sym.endsWith('-EQ') || sym.startsWith('BSE:SENSEX')) {
|
| 950 |
+
if(data[sym].ltp > 0) refPrice = data[sym].ltp;
|
| 951 |
+
}
|
| 952 |
+
// Fallback to Future if Spot not found or is 0
|
| 953 |
+
else if(sym.endsWith('FUT') && refPrice === 0) {
|
| 954 |
+
if(data[sym].ltp > 0) refPrice = data[sym].ltp;
|
| 955 |
+
}
|
| 956 |
+
// Check if options exist in this packet
|
| 957 |
+
if(sym.endsWith('CE') || sym.endsWith('PE')) hasOptions = true;
|
| 958 |
+
}
|
| 959 |
+
|
| 960 |
+
// CRITERIA 1: Missing Reference Price (Corrupted)
|
| 961 |
+
if(refPrice === 0) return false;
|
| 962 |
+
|
| 963 |
+
// CRITERIA 2: Validate ATM ± Range Data (Zero Qty Check)
|
| 964 |
+
if(hasOptions) {
|
| 965 |
+
// Extract valid strikes from symbol keys
|
| 966 |
+
let strikes = [];
|
| 967 |
+
for(let sym in data) {
|
| 968 |
+
// Regex matches Fyers symbols ending in Number+CE/PE (e.g., ...22100CE)
|
| 969 |
+
let match = sym.match(/(\d+)(CE|PE)$/);
|
| 970 |
+
if(match && match[1]) {
|
| 971 |
+
strikes.push({
|
| 972 |
+
k: parseFloat(match[1]),
|
| 973 |
+
d: data[sym]
|
| 974 |
+
});
|
| 975 |
+
}
|
| 976 |
+
}
|
| 977 |
+
|
| 978 |
+
if(strikes.length > 0) {
|
| 979 |
+
// Find ATM Strike based on Ref Price
|
| 980 |
+
strikes.sort((a,b) => a.k - b.k);
|
| 981 |
+
let closest = strikes.reduce((prev, curr) =>
|
| 982 |
+
Math.abs(curr.k - refPrice) < Math.abs(prev.k - refPrice) ? curr : prev
|
| 983 |
+
);
|
| 984 |
+
|
| 985 |
+
// Get unique strikes to determine index range
|
| 986 |
+
let uniqKs = [...new Set(strikes.map(i => i.k))].sort((a,b)=>a-b);
|
| 987 |
+
let atmIndex = uniqKs.indexOf(closest.k);
|
| 988 |
+
|
| 989 |
+
// Define Range: ATM ± 5 strikes
|
| 990 |
+
let start = Math.max(0, atmIndex - 5);
|
| 991 |
+
let end = Math.min(uniqKs.length - 1, atmIndex + 5);
|
| 992 |
+
let targetKs = uniqKs.slice(start, end + 1);
|
| 993 |
+
|
| 994 |
+
// Check specific options in this range
|
| 995 |
+
for(let item of strikes) {
|
| 996 |
+
if(targetKs.includes(item.k)) {
|
| 997 |
+
let qB = item.d.totalbuyqty;
|
| 998 |
+
let qS = item.d.totalsellqty;
|
| 999 |
+
|
| 1000 |
+
// THE CHECK: If both Buy AND Sell Qty are 0 or undefined -> Data is inefficient
|
| 1001 |
+
if((!qB && !qS) || (qB === 0 && qS === 0)) {
|
| 1002 |
+
return false; // Mark as Bad
|
| 1003 |
+
}
|
| 1004 |
+
}
|
| 1005 |
+
}
|
| 1006 |
+
}
|
| 1007 |
+
}
|
| 1008 |
+
|
| 1009 |
+
return true; // Data passed checks
|
| 1010 |
+
}
|
| 1011 |
+
|
| 1012 |
function renderSecondBar() {
|
| 1013 |
const bar = document.getElementById('secBar');
|
| 1014 |
+
bar.innerHTML = CACHED_TIMELINE.map((item, idx) => {
|
| 1015 |
+
// Run validation on the data packet ('d')
|
| 1016 |
+
const isValid = validateSnapshot(item.d);
|
| 1017 |
+
const statusClass = isValid ? '' : 'bad';
|
| 1018 |
+
|
| 1019 |
+
return `
|
| 1020 |
+
<div class="sec-pill ${idx===0?'active':''} ${statusClass}"
|
| 1021 |
onclick="selectSnapshot(${idx})"
|
| 1022 |
+
id="pill-${idx}"
|
| 1023 |
+
title="${isValid ? 'Data OK' : 'Data Inefficient (Zero Qty or Missing Ref)'}">
|
| 1024 |
:${item.s}
|
| 1025 |
</div>
|
| 1026 |
+
`}).join('');
|
| 1027 |
}
|
| 1028 |
|
| 1029 |
function selectSnapshot(idx) {
|