Update app.py
Browse files
app.py
CHANGED
|
@@ -427,6 +427,19 @@ HTML_TEMPLATE = """
|
|
| 427 |
|
| 428 |
.form-section label { font-size: 11px; text-transform: uppercase; color: var(--text-secondary); font-weight: 600; margin-bottom: 6px; display: block; }
|
| 429 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 430 |
/* SERIES ROW STRUCTURE (POLISHED UI) */
|
| 431 |
.series-row {
|
| 432 |
background: var(--bg-panel); padding: 12px; border-radius: 8px; border: 1px solid var(--border); margin-bottom: 10px;
|
|
@@ -528,13 +541,19 @@ HTML_TEMPLATE = """
|
|
| 528 |
<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">
|
| 529 |
<div class="form-section">
|
| 530 |
<label>Symbol</label>
|
| 531 |
-
<
|
| 532 |
-
|
|
|
|
|
|
|
|
|
|
| 533 |
</div>
|
| 534 |
<div class="form-section">
|
| 535 |
<label>Expiry</label>
|
| 536 |
-
<
|
| 537 |
-
|
|
|
|
|
|
|
|
|
|
| 538 |
</div>
|
| 539 |
</div>
|
| 540 |
|
|
@@ -550,8 +569,11 @@ HTML_TEMPLATE = """
|
|
| 550 |
<div id="cfgNormalFields">
|
| 551 |
<div class="form-section">
|
| 552 |
<label>Instrument</label>
|
| 553 |
-
<
|
| 554 |
-
|
|
|
|
|
|
|
|
|
|
| 555 |
</div>
|
| 556 |
</div>
|
| 557 |
|
|
@@ -616,6 +638,69 @@ HTML_TEMPLATE = """
|
|
| 616 |
</div>
|
| 617 |
|
| 618 |
<script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 619 |
const App = {
|
| 620 |
state: { date: null, mode: 'LIVE', columns: 3, layout: [[],[],[]], charts: {}, refreshRate: 15 },
|
| 621 |
presets: {
|
|
@@ -1061,10 +1146,10 @@ const Config = {
|
|
| 1061 |
|
| 1062 |
this.updatePresetsDropdown();
|
| 1063 |
|
| 1064 |
-
|
| 1065 |
const res = await fetch(`/api/roots?date=${App.state.date}`);
|
| 1066 |
const roots = await res.json();
|
| 1067 |
-
|
| 1068 |
|
| 1069 |
const cfg = App.state.charts[id].config;
|
| 1070 |
document.getElementById('cfgTF').value = cfg.timeframe || '1min';
|
|
@@ -1118,8 +1203,8 @@ const Config = {
|
|
| 1118 |
|
| 1119 |
async onRootChange(preExp, preInst) {
|
| 1120 |
const root = document.getElementById('cfgRoot').value.toUpperCase();
|
| 1121 |
-
|
| 1122 |
-
|
| 1123 |
|
| 1124 |
if (!preExp) document.getElementById('cfgExp').value = '';
|
| 1125 |
if (!preInst) document.getElementById('cfgInst').value = '';
|
|
@@ -1128,14 +1213,13 @@ const Config = {
|
|
| 1128 |
|
| 1129 |
const res = await fetch(`/api/expiries?date=${App.state.date}&root=${root}`);
|
| 1130 |
const exps = await res.json();
|
| 1131 |
-
|
| 1132 |
|
| 1133 |
if(preExp && exps.includes(preExp)) {
|
| 1134 |
document.getElementById('cfgExp').value = preExp;
|
| 1135 |
await this.onExpChange(preInst);
|
| 1136 |
}
|
| 1137 |
else if (exps.length > 0) {
|
| 1138 |
-
// Auto-select logic
|
| 1139 |
const auto = await fetch(`/api/auto_config?date=${App.state.date}&root=${root}`).then(r=>r.json());
|
| 1140 |
if(auto.current) {
|
| 1141 |
document.getElementById('cfgExp').value = auto.current.expiry;
|
|
@@ -1146,20 +1230,17 @@ const Config = {
|
|
| 1146 |
|
| 1147 |
async onExpChange(preInst) {
|
| 1148 |
const root = document.getElementById('cfgRoot').value.toUpperCase();
|
| 1149 |
-
const exp = document.getElementById('cfgExp').value;
|
| 1150 |
-
|
| 1151 |
-
iList.innerHTML = '';
|
| 1152 |
|
| 1153 |
if (!root || !exp) return;
|
| 1154 |
|
| 1155 |
const res = await fetch(`/api/instruments?date=${App.state.date}&root=${root}&expiry=${exp}`);
|
| 1156 |
const data = await res.json();
|
| 1157 |
|
| 1158 |
-
|
| 1159 |
-
data.
|
| 1160 |
-
data.options.forEach(i => html += `<option value="${i}"></option>`);
|
| 1161 |
|
| 1162 |
-
iList.innerHTML = html;
|
| 1163 |
if(preInst) document.getElementById('cfgInst').value = preInst;
|
| 1164 |
},
|
| 1165 |
|
|
@@ -1246,8 +1327,8 @@ const Config = {
|
|
| 1246 |
}
|
| 1247 |
else {
|
| 1248 |
const root = document.getElementById('cfgRoot').value.toUpperCase();
|
| 1249 |
-
const exp = document.getElementById('cfgExp').value;
|
| 1250 |
-
const inst = document.getElementById('cfgInst').value;
|
| 1251 |
|
| 1252 |
if(!root || !exp) return alert("Incomplete Details. Please type or select valid options.");
|
| 1253 |
|
|
|
|
| 427 |
|
| 428 |
.form-section label { font-size: 11px; text-transform: uppercase; color: var(--text-secondary); font-weight: 600; margin-bottom: 6px; display: block; }
|
| 429 |
|
| 430 |
+
/* CUSTOM DROPDOWN UI */
|
| 431 |
+
.input-wrapper { position: relative; width: 100%; }
|
| 432 |
+
.input-arrow { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); pointer-events: none; color: var(--text-secondary); font-size: 10px; }
|
| 433 |
+
.custom-dd {
|
| 434 |
+
position: absolute; top: 100%; left: 0; right: 0; background: var(--bg-panel); border: 1px solid var(--border);
|
| 435 |
+
border-radius: 6px; max-height: 200px; overflow-y: auto; z-index: 1000; display: none;
|
| 436 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.5); margin-top: 4px;
|
| 437 |
+
}
|
| 438 |
+
.custom-dd-opt { padding: 8px 12px; font-size: 12px; cursor: pointer; color: var(--text-primary); border-bottom: 1px solid var(--border); }
|
| 439 |
+
.custom-dd-opt:last-child { border-bottom: none; }
|
| 440 |
+
.custom-dd-opt:hover { background: var(--bg-input); color: var(--accent); }
|
| 441 |
+
.custom-dd-group { padding: 6px 12px; font-size: 10px; font-weight: 700; color: var(--text-secondary); background: var(--bg-body); text-transform: uppercase; }
|
| 442 |
+
|
| 443 |
/* SERIES ROW STRUCTURE (POLISHED UI) */
|
| 444 |
.series-row {
|
| 445 |
background: var(--bg-panel); padding: 12px; border-radius: 8px; border: 1px solid var(--border); margin-bottom: 10px;
|
|
|
|
| 541 |
<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">
|
| 542 |
<div class="form-section">
|
| 543 |
<label>Symbol</label>
|
| 544 |
+
<div class="input-wrapper">
|
| 545 |
+
<input type="text" id="cfgRoot" onfocus="CustomDD.open('cfgRoot')" oninput="CustomDD.filter('cfgRoot')" style="width:100%; text-transform: uppercase;" placeholder="Type or select..." autocomplete="off">
|
| 546 |
+
<div class="input-arrow">▼</div>
|
| 547 |
+
<div id="dd-cfgRoot" class="custom-dd"></div>
|
| 548 |
+
</div>
|
| 549 |
</div>
|
| 550 |
<div class="form-section">
|
| 551 |
<label>Expiry</label>
|
| 552 |
+
<div class="input-wrapper">
|
| 553 |
+
<input type="text" id="cfgExp" onfocus="CustomDD.open('cfgExp')" oninput="CustomDD.filter('cfgExp')" style="width:100%; text-transform: uppercase;" placeholder="Type or select..." autocomplete="off">
|
| 554 |
+
<div class="input-arrow">▼</div>
|
| 555 |
+
<div id="dd-cfgExp" class="custom-dd"></div>
|
| 556 |
+
</div>
|
| 557 |
</div>
|
| 558 |
</div>
|
| 559 |
|
|
|
|
| 569 |
<div id="cfgNormalFields">
|
| 570 |
<div class="form-section">
|
| 571 |
<label>Instrument</label>
|
| 572 |
+
<div class="input-wrapper">
|
| 573 |
+
<input type="text" id="cfgInst" onfocus="CustomDD.open('cfgInst')" oninput="CustomDD.filter('cfgInst')" style="width:100%; text-transform: uppercase;" placeholder="Type or select..." autocomplete="off">
|
| 574 |
+
<div class="input-arrow">▼</div>
|
| 575 |
+
<div id="dd-cfgInst" class="custom-dd"></div>
|
| 576 |
+
</div>
|
| 577 |
</div>
|
| 578 |
</div>
|
| 579 |
|
|
|
|
| 638 |
</div>
|
| 639 |
|
| 640 |
<script>
|
| 641 |
+
// --- CUSTOM SEARCHABLE DROPDOWN LOGIC ---
|
| 642 |
+
document.addEventListener('click', (e) => {
|
| 643 |
+
if (!e.target.closest('.input-wrapper')) {
|
| 644 |
+
document.querySelectorAll('.custom-dd').forEach(el => el.style.display = 'none');
|
| 645 |
+
}
|
| 646 |
+
});
|
| 647 |
+
|
| 648 |
+
const CustomDD = {
|
| 649 |
+
data: { cfgRoot: [], cfgExp: [], cfgInst:[] },
|
| 650 |
+
|
| 651 |
+
clear(id) {
|
| 652 |
+
this.data[id] =[];
|
| 653 |
+
document.getElementById(id).value = '';
|
| 654 |
+
},
|
| 655 |
+
set(id, items, groupName = null) {
|
| 656 |
+
if (!groupName) {
|
| 657 |
+
this.data[id] = items.map(i => ({val: i, group: null}));
|
| 658 |
+
} else {
|
| 659 |
+
this.data[id].push(...items.map(i => ({val: i, group: groupName})));
|
| 660 |
+
}
|
| 661 |
+
},
|
| 662 |
+
open(id) {
|
| 663 |
+
document.querySelectorAll('.custom-dd').forEach(el => el.style.display = 'none');
|
| 664 |
+
const inp = document.getElementById(id);
|
| 665 |
+
inp.select();
|
| 666 |
+
this.render(id, ''); // Show all options
|
| 667 |
+
document.getElementById(`dd-${id}`).style.display = 'block';
|
| 668 |
+
},
|
| 669 |
+
filter(id) {
|
| 670 |
+
this.render(id, document.getElementById(id).value);
|
| 671 |
+
document.getElementById(`dd-${id}`).style.display = 'block';
|
| 672 |
+
},
|
| 673 |
+
render(id, query) {
|
| 674 |
+
const el = document.getElementById(`dd-${id}`);
|
| 675 |
+
const q = query.toLowerCase();
|
| 676 |
+
let html = '';
|
| 677 |
+
let currentGroup = null;
|
| 678 |
+
|
| 679 |
+
const filtered = this.data[id].filter(item => item.val.toLowerCase().includes(q));
|
| 680 |
+
|
| 681 |
+
if (filtered.length === 0) {
|
| 682 |
+
el.innerHTML = '<div class="custom-dd-opt" style="color:var(--text-secondary); cursor:default;">No matches found</div>';
|
| 683 |
+
return;
|
| 684 |
+
}
|
| 685 |
+
|
| 686 |
+
filtered.forEach(item => {
|
| 687 |
+
if (item.group && item.group !== currentGroup) {
|
| 688 |
+
html += `<div class="custom-dd-group">${item.group}</div>`;
|
| 689 |
+
currentGroup = item.group;
|
| 690 |
+
}
|
| 691 |
+
html += `<div class="custom-dd-opt" onclick="CustomDD.pick('${id}', '${item.val}')">${item.val}</div>`;
|
| 692 |
+
});
|
| 693 |
+
el.innerHTML = html;
|
| 694 |
+
},
|
| 695 |
+
pick(id, val) {
|
| 696 |
+
document.getElementById(id).value = val;
|
| 697 |
+
document.getElementById(`dd-${id}`).style.display = 'none';
|
| 698 |
+
if(id === 'cfgRoot') Config.onRootChange();
|
| 699 |
+
if(id === 'cfgExp') Config.onExpChange();
|
| 700 |
+
}
|
| 701 |
+
};
|
| 702 |
+
// ----------------------------------------
|
| 703 |
+
|
| 704 |
const App = {
|
| 705 |
state: { date: null, mode: 'LIVE', columns: 3, layout: [[],[],[]], charts: {}, refreshRate: 15 },
|
| 706 |
presets: {
|
|
|
|
| 1146 |
|
| 1147 |
this.updatePresetsDropdown();
|
| 1148 |
|
| 1149 |
+
CustomDD.clear('cfgRoot');
|
| 1150 |
const res = await fetch(`/api/roots?date=${App.state.date}`);
|
| 1151 |
const roots = await res.json();
|
| 1152 |
+
CustomDD.set('cfgRoot', roots);
|
| 1153 |
|
| 1154 |
const cfg = App.state.charts[id].config;
|
| 1155 |
document.getElementById('cfgTF').value = cfg.timeframe || '1min';
|
|
|
|
| 1203 |
|
| 1204 |
async onRootChange(preExp, preInst) {
|
| 1205 |
const root = document.getElementById('cfgRoot').value.toUpperCase();
|
| 1206 |
+
CustomDD.clear('cfgExp');
|
| 1207 |
+
CustomDD.clear('cfgInst');
|
| 1208 |
|
| 1209 |
if (!preExp) document.getElementById('cfgExp').value = '';
|
| 1210 |
if (!preInst) document.getElementById('cfgInst').value = '';
|
|
|
|
| 1213 |
|
| 1214 |
const res = await fetch(`/api/expiries?date=${App.state.date}&root=${root}`);
|
| 1215 |
const exps = await res.json();
|
| 1216 |
+
CustomDD.set('cfgExp', exps);
|
| 1217 |
|
| 1218 |
if(preExp && exps.includes(preExp)) {
|
| 1219 |
document.getElementById('cfgExp').value = preExp;
|
| 1220 |
await this.onExpChange(preInst);
|
| 1221 |
}
|
| 1222 |
else if (exps.length > 0) {
|
|
|
|
| 1223 |
const auto = await fetch(`/api/auto_config?date=${App.state.date}&root=${root}`).then(r=>r.json());
|
| 1224 |
if(auto.current) {
|
| 1225 |
document.getElementById('cfgExp').value = auto.current.expiry;
|
|
|
|
| 1230 |
|
| 1231 |
async onExpChange(preInst) {
|
| 1232 |
const root = document.getElementById('cfgRoot').value.toUpperCase();
|
| 1233 |
+
const exp = document.getElementById('cfgExp').value.toUpperCase();
|
| 1234 |
+
CustomDD.clear('cfgInst');
|
|
|
|
| 1235 |
|
| 1236 |
if (!root || !exp) return;
|
| 1237 |
|
| 1238 |
const res = await fetch(`/api/instruments?date=${App.state.date}&root=${root}&expiry=${exp}`);
|
| 1239 |
const data = await res.json();
|
| 1240 |
|
| 1241 |
+
if (data.spot_fut.length > 0) CustomDD.set('cfgInst', data.spot_fut, 'Futures & Equity');
|
| 1242 |
+
if (data.options.length > 0) CustomDD.set('cfgInst', data.options, 'Options');
|
|
|
|
| 1243 |
|
|
|
|
| 1244 |
if(preInst) document.getElementById('cfgInst').value = preInst;
|
| 1245 |
},
|
| 1246 |
|
|
|
|
| 1327 |
}
|
| 1328 |
else {
|
| 1329 |
const root = document.getElementById('cfgRoot').value.toUpperCase();
|
| 1330 |
+
const exp = document.getElementById('cfgExp').value.toUpperCase();
|
| 1331 |
+
const inst = document.getElementById('cfgInst').value.toUpperCase();
|
| 1332 |
|
| 1333 |
if(!root || !exp) return alert("Incomplete Details. Please type or select valid options.");
|
| 1334 |
|