Update app.py
Browse files
app.py
CHANGED
|
@@ -108,7 +108,7 @@ def get_expiries(date: str, root: str):
|
|
| 108 |
def get_instruments(date: str, root: str, expiry: str):
|
| 109 |
root = root.upper()
|
| 110 |
data = sb_rpc("get_instruments_for_expiry", {"p_date": date, "p_root": root, "p_expiry": expiry})
|
| 111 |
-
instruments =
|
| 112 |
|
| 113 |
spot_fut =[]
|
| 114 |
options =[]
|
|
@@ -121,7 +121,7 @@ def get_instruments(date: str, root: str, expiry: str):
|
|
| 121 |
return JSONResponse({"spot_fut": sorted(spot_fut), "options": sorted(options)})
|
| 122 |
|
| 123 |
# ==========================================
|
| 124 |
-
# 4. API ROUTES - DATA FETCHING (EGRESS OPTIMIZED
|
| 125 |
# ==========================================
|
| 126 |
|
| 127 |
@app.post("/api/fetch_series")
|
|
@@ -135,7 +135,7 @@ async def fetch_series(
|
|
| 135 |
end_time: str = Body(...),
|
| 136 |
mode: str = Body("normal"),
|
| 137 |
atm_range: int = Body(5),
|
| 138 |
-
req_vars: List[str] = Body([]) # EGRESS REDUCTION: Only
|
| 139 |
):
|
| 140 |
root = root.upper()
|
| 141 |
t_start_dt = pd.to_datetime(f"{date} {start_time}:00")
|
|
@@ -189,7 +189,7 @@ async def fetch_series(
|
|
| 189 |
if not all_records: return JSONResponse({"error": "No valid data found in range.", "labels":[]})
|
| 190 |
|
| 191 |
df = pd.DataFrame(all_records)
|
| 192 |
-
df.rename(columns={"tick_ts": "ts"
|
| 193 |
|
| 194 |
df['ts'] = pd.to_datetime(df['ts'])
|
| 195 |
df.drop_duplicates(subset=['ts'], inplace=True)
|
|
@@ -199,19 +199,19 @@ async def fetch_series(
|
|
| 199 |
tf_map = {"1min": "1min", "3min": "3min", "5min": "5min", "15min": "15min", "30min": "30min", "1hour": "1h"}
|
| 200 |
panda_tf = tf_map.get(timeframe, "1min")
|
| 201 |
|
| 202 |
-
#
|
| 203 |
-
|
| 204 |
-
agg_dict = { col: "last" for col in["price", "volume", "oi", "buy_qty", "sell_qty", "cb", "cs", "pb", "ps"] if col in df.columns }
|
| 205 |
|
| 206 |
resampled = df.resample(panda_tf).agg(agg_dict).ffill().fillna(0)
|
| 207 |
|
| 208 |
-
# EGRESS OPTIMIZATION: Process Only Required Arrays dynamically parsed from formula!
|
| 209 |
response_data = {"labels": resampled.index.strftime('%H:%M').tolist(), "P": resampled['price'].tolist() if 'price' in resampled else[]}
|
| 210 |
|
|
|
|
|
|
|
| 211 |
for req in req_vars:
|
| 212 |
-
|
| 213 |
-
col
|
| 214 |
-
|
| 215 |
|
| 216 |
return JSONResponse(response_data)
|
| 217 |
|
|
@@ -524,22 +524,17 @@ const CustomDD = {
|
|
| 524 |
const App = {
|
| 525 |
state: { date: null, mode: 'LIVE', columns: 3, layout: [[],[],[]], charts: {}, refreshRate: 15 },
|
| 526 |
presets: {
|
| 527 |
-
"
|
| 528 |
{ formula: 'P', color1: '#2f81f7', cond: 'none', label: 'Price', axis: 'left' },
|
| 529 |
-
{ formula: '
|
| 530 |
-
{ formula: '
|
| 531 |
-
],
|
| 532 |
-
"Advanced Option Model":[
|
| 533 |
-
{ formula: 'P', color1: '#2f81f7', cond: 'none', label: 'Spot Price', axis: 'left' },
|
| 534 |
-
{ formula: 'CB + PS', color1: '#238636', cond: 'none', label: 'Net Buy Flow', axis: 'right' },
|
| 535 |
-
{ formula: 'CS + PB', color1: '#da3633', cond: 'none', label: 'Net Sell Flow', axis: 'right' }
|
| 536 |
]
|
| 537 |
},
|
| 538 |
async init() {
|
| 539 |
CustomDD.init(); await this.loadDates();
|
| 540 |
-
const storedPresets = localStorage.getItem('
|
| 541 |
if(storedPresets) try { this.presets = { ...this.presets, ...JSON.parse(storedPresets) }; } catch(e){}
|
| 542 |
-
const saved = localStorage.getItem('
|
| 543 |
if (saved) {
|
| 544 |
try { this.state = { ...this.state, ...JSON.parse(saved) }; if(this.state.layout.length !== this.state.columns) this.setLayout(this.state.columns); }
|
| 545 |
catch(e) { this.initDefault(); }
|
|
@@ -570,9 +565,9 @@ const App = {
|
|
| 570 |
},
|
| 571 |
save() {
|
| 572 |
const cleanCharts = {}; for(let id in this.state.charts) cleanCharts[id] = { config: this.state.charts[id].config };
|
| 573 |
-
localStorage.setItem('
|
| 574 |
},
|
| 575 |
-
reset() { if(confirm("Reset Workspace?")) { localStorage.removeItem('
|
| 576 |
setLayout(n) {
|
| 577 |
const all = this.state.layout.flat(); this.state.columns = n; this.state.layout = Array.from({length: n}, () =>[]);
|
| 578 |
all.forEach((id, i) => this.state.layout[i % n].push(id));
|
|
@@ -640,7 +635,7 @@ const App = {
|
|
| 640 |
|
| 641 |
const ChartLogic = {
|
| 642 |
extractVars(formula) {
|
| 643 |
-
const validVars =['CB','CS','PB','PS','P','V','OI'
|
| 644 |
let requested =[];
|
| 645 |
validVars.forEach(v => { if (new RegExp('\\b' + v + '\\b').test(formula)) requested.push(v); });
|
| 646 |
return requested;
|
|
@@ -802,25 +797,24 @@ const Config = {
|
|
| 802 |
document.getElementById('lblInst').innerText = mode === 'normal' ? 'Instrument' : 'Reference Spot/Fut (For ATM Calc)';
|
| 803 |
document.getElementById('advInstHint').style.display = mode === 'advanced' ? 'block' : 'none';
|
| 804 |
this.updateInfoTooltip(mode); this.updatePresetsDropdown();
|
| 805 |
-
this.loadPreset(
|
| 806 |
},
|
| 807 |
|
| 808 |
updateInfoTooltip(mode) {
|
| 809 |
const icon = document.getElementById('infoIcon'); if (!icon) return;
|
| 810 |
-
|
| 811 |
-
else { icon.title = "Standard: P, V, OI, B, S \\nSpecific Option Selected: CB/CS or PB/PS are automatically mapped from B/S."; icon.style.color = "var(--text-secondary)"; icon.style.borderColor = "var(--border)"; }
|
| 812 |
},
|
| 813 |
|
| 814 |
updatePresetsDropdown() { document.getElementById('cfgPresetsDropdown').innerHTML = Object.keys(App.presets).map(k => `<option value="${k}">${k}</option>`).join(''); },
|
| 815 |
loadPreset(presetName = null) { const name = presetName || document.getElementById('cfgPresetsDropdown').value; if(name && App.presets[name]) { document.getElementById('cfgPresetsDropdown').value = name; this.renderSeries(App.presets[name]); } },
|
| 816 |
savePreset() {
|
| 817 |
const name = prompt("Enter a name for this Quick Configuration:", "Custom Model"); if(!name) return;
|
| 818 |
-
App.presets[name] = this.extractSeriesFromDOM(); localStorage.setItem('
|
| 819 |
this.updatePresetsDropdown(); document.getElementById('cfgPresetsDropdown').value = name;
|
| 820 |
},
|
| 821 |
deletePreset() {
|
| 822 |
-
const name = document.getElementById('cfgPresetsDropdown').value; if(name.startsWith('
|
| 823 |
-
if(confirm(`Delete model '${name}'?`)) { delete App.presets[name]; localStorage.setItem('
|
| 824 |
},
|
| 825 |
|
| 826 |
async open(id) {
|
|
@@ -843,7 +837,7 @@ const Config = {
|
|
| 843 |
document.getElementById('advInstHint').style.display = this.currentMode === 'advanced' ? 'block' : 'none';
|
| 844 |
|
| 845 |
this.updateInfoTooltip(this.currentMode);
|
| 846 |
-
if (cfg.series && cfg.series.length > 0) this.renderSeries(cfg.series); else this.loadPreset(
|
| 847 |
|
| 848 |
document.getElementById('cfgRoot').value = cfg.root || ''; document.getElementById('cfgExp').value = cfg.expiry || ''; document.getElementById('cfgInst').value = cfg.instrument || '';
|
| 849 |
if(cfg.root) { await this.onRootChange(cfg.expiry, cfg.instrument); }
|
|
@@ -855,7 +849,7 @@ const Config = {
|
|
| 855 |
document.getElementById('cfgLoading').style.display = 'none'; document.getElementById('cfgContent').style.display = 'block';
|
| 856 |
document.getElementById('cfgGlobalOnly').style.display = 'block'; document.getElementById('cfgChartOnly').style.display = 'none';
|
| 857 |
document.getElementById('cfgRefresh').value = App.state.refreshRate || 15;
|
| 858 |
-
this.updatePresetsDropdown(); this.loadPreset("
|
| 859 |
},
|
| 860 |
close() { document.getElementById('configModal').style.display = 'none'; },
|
| 861 |
|
|
|
|
| 108 |
def get_instruments(date: str, root: str, expiry: str):
|
| 109 |
root = root.upper()
|
| 110 |
data = sb_rpc("get_instruments_for_expiry", {"p_date": date, "p_root": root, "p_expiry": expiry})
|
| 111 |
+
instruments =[row['instrument_name'] for row in data]
|
| 112 |
|
| 113 |
spot_fut =[]
|
| 114 |
options =[]
|
|
|
|
| 121 |
return JSONResponse({"spot_fut": sorted(spot_fut), "options": sorted(options)})
|
| 122 |
|
| 123 |
# ==========================================
|
| 124 |
+
# 4. API ROUTES - DATA FETCHING (EGRESS OPTIMIZED)
|
| 125 |
# ==========================================
|
| 126 |
|
| 127 |
@app.post("/api/fetch_series")
|
|
|
|
| 135 |
end_time: str = Body(...),
|
| 136 |
mode: str = Body("normal"),
|
| 137 |
atm_range: int = Body(5),
|
| 138 |
+
req_vars: List[str] = Body([]) # EGRESS REDUCTION: Only requested logic passes through
|
| 139 |
):
|
| 140 |
root = root.upper()
|
| 141 |
t_start_dt = pd.to_datetime(f"{date} {start_time}:00")
|
|
|
|
| 189 |
if not all_records: return JSONResponse({"error": "No valid data found in range.", "labels":[]})
|
| 190 |
|
| 191 |
df = pd.DataFrame(all_records)
|
| 192 |
+
df.rename(columns={"tick_ts": "ts"}, inplace=True)
|
| 193 |
|
| 194 |
df['ts'] = pd.to_datetime(df['ts'])
|
| 195 |
df.drop_duplicates(subset=['ts'], inplace=True)
|
|
|
|
| 199 |
tf_map = {"1min": "1min", "3min": "3min", "5min": "5min", "15min": "15min", "30min": "30min", "1hour": "1h"}
|
| 200 |
panda_tf = tf_map.get(timeframe, "1min")
|
| 201 |
|
| 202 |
+
# Strictly take the LAST cumulative Fyers variable output for the candle
|
| 203 |
+
agg_dict = { col: "last" for col in["price", "vol", "oi", "cb", "cs", "pb", "ps"] if col in df.columns }
|
|
|
|
| 204 |
|
| 205 |
resampled = df.resample(panda_tf).agg(agg_dict).ffill().fillna(0)
|
| 206 |
|
|
|
|
| 207 |
response_data = {"labels": resampled.index.strftime('%H:%M').tolist(), "P": resampled['price'].tolist() if 'price' in resampled else[]}
|
| 208 |
|
| 209 |
+
# Maps what frontend asks for (e.g., 'V') to the database columns (e.g., 'vol')
|
| 210 |
+
col_map = {"V": "vol", "OI": "oi", "CB": "cb", "CS": "cs", "PB": "pb", "PS": "ps"}
|
| 211 |
for req in req_vars:
|
| 212 |
+
col = col_map.get(req.upper())
|
| 213 |
+
if col and col in resampled.columns:
|
| 214 |
+
response_data[req.upper()] = resampled[col].tolist()
|
| 215 |
|
| 216 |
return JSONResponse(response_data)
|
| 217 |
|
|
|
|
| 524 |
const App = {
|
| 525 |
state: { date: null, mode: 'LIVE', columns: 3, layout: [[],[],[]], charts: {}, refreshRate: 15 },
|
| 526 |
presets: {
|
| 527 |
+
"Default Flow":[
|
| 528 |
{ formula: 'P', color1: '#2f81f7', cond: 'none', label: 'Price', axis: 'left' },
|
| 529 |
+
{ formula: 'CB + PS', color1: '#238636', cond: 'none', label: 'Bullish Flow', axis: 'right' },
|
| 530 |
+
{ formula: 'PB + CS', color1: '#da3633', cond: 'none', label: 'Bearish Flow', axis: 'right' }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 531 |
]
|
| 532 |
},
|
| 533 |
async init() {
|
| 534 |
CustomDD.init(); await this.loadDates();
|
| 535 |
+
const storedPresets = localStorage.getItem('dc_pro_v5_presets');
|
| 536 |
if(storedPresets) try { this.presets = { ...this.presets, ...JSON.parse(storedPresets) }; } catch(e){}
|
| 537 |
+
const saved = localStorage.getItem('dc_pro_v5_final');
|
| 538 |
if (saved) {
|
| 539 |
try { this.state = { ...this.state, ...JSON.parse(saved) }; if(this.state.layout.length !== this.state.columns) this.setLayout(this.state.columns); }
|
| 540 |
catch(e) { this.initDefault(); }
|
|
|
|
| 565 |
},
|
| 566 |
save() {
|
| 567 |
const cleanCharts = {}; for(let id in this.state.charts) cleanCharts[id] = { config: this.state.charts[id].config };
|
| 568 |
+
localStorage.setItem('dc_pro_v5_final', JSON.stringify({ ...this.state, charts: cleanCharts }));
|
| 569 |
},
|
| 570 |
+
reset() { if(confirm("Reset Workspace?")) { localStorage.removeItem('dc_pro_v5_final'); location.reload(); } },
|
| 571 |
setLayout(n) {
|
| 572 |
const all = this.state.layout.flat(); this.state.columns = n; this.state.layout = Array.from({length: n}, () =>[]);
|
| 573 |
all.forEach((id, i) => this.state.layout[i % n].push(id));
|
|
|
|
| 635 |
|
| 636 |
const ChartLogic = {
|
| 637 |
extractVars(formula) {
|
| 638 |
+
const validVars =['CB','CS','PB','PS','P','V','OI'];
|
| 639 |
let requested =[];
|
| 640 |
validVars.forEach(v => { if (new RegExp('\\b' + v + '\\b').test(formula)) requested.push(v); });
|
| 641 |
return requested;
|
|
|
|
| 797 |
document.getElementById('lblInst').innerText = mode === 'normal' ? 'Instrument' : 'Reference Spot/Fut (For ATM Calc)';
|
| 798 |
document.getElementById('advInstHint').style.display = mode === 'advanced' ? 'block' : 'none';
|
| 799 |
this.updateInfoTooltip(mode); this.updatePresetsDropdown();
|
| 800 |
+
this.loadPreset("Default Flow");
|
| 801 |
},
|
| 802 |
|
| 803 |
updateInfoTooltip(mode) {
|
| 804 |
const icon = document.getElementById('infoIcon'); if (!icon) return;
|
| 805 |
+
icon.title = "Standard Variables Available:\\nP, V, OI, CB, CS, PB, PS\\nSupports Natural Formulas: e.g. CB + PS";
|
|
|
|
| 806 |
},
|
| 807 |
|
| 808 |
updatePresetsDropdown() { document.getElementById('cfgPresetsDropdown').innerHTML = Object.keys(App.presets).map(k => `<option value="${k}">${k}</option>`).join(''); },
|
| 809 |
loadPreset(presetName = null) { const name = presetName || document.getElementById('cfgPresetsDropdown').value; if(name && App.presets[name]) { document.getElementById('cfgPresetsDropdown').value = name; this.renderSeries(App.presets[name]); } },
|
| 810 |
savePreset() {
|
| 811 |
const name = prompt("Enter a name for this Quick Configuration:", "Custom Model"); if(!name) return;
|
| 812 |
+
App.presets[name] = this.extractSeriesFromDOM(); localStorage.setItem('dc_pro_v5_presets', JSON.stringify(App.presets));
|
| 813 |
this.updatePresetsDropdown(); document.getElementById('cfgPresetsDropdown').value = name;
|
| 814 |
},
|
| 815 |
deletePreset() {
|
| 816 |
+
const name = document.getElementById('cfgPresetsDropdown').value; if(name.startsWith('Default Flow')) return alert("Cannot delete default models.");
|
| 817 |
+
if(confirm(`Delete model '${name}'?`)) { delete App.presets[name]; localStorage.setItem('dc_pro_v5_presets', JSON.stringify(App.presets)); this.updatePresetsDropdown(); this.loadPreset(); }
|
| 818 |
},
|
| 819 |
|
| 820 |
async open(id) {
|
|
|
|
| 837 |
document.getElementById('advInstHint').style.display = this.currentMode === 'advanced' ? 'block' : 'none';
|
| 838 |
|
| 839 |
this.updateInfoTooltip(this.currentMode);
|
| 840 |
+
if (cfg.series && cfg.series.length > 0) this.renderSeries(cfg.series); else this.loadPreset("Default Flow");
|
| 841 |
|
| 842 |
document.getElementById('cfgRoot').value = cfg.root || ''; document.getElementById('cfgExp').value = cfg.expiry || ''; document.getElementById('cfgInst').value = cfg.instrument || '';
|
| 843 |
if(cfg.root) { await this.onRootChange(cfg.expiry, cfg.instrument); }
|
|
|
|
| 849 |
document.getElementById('cfgLoading').style.display = 'none'; document.getElementById('cfgContent').style.display = 'block';
|
| 850 |
document.getElementById('cfgGlobalOnly').style.display = 'block'; document.getElementById('cfgChartOnly').style.display = 'none';
|
| 851 |
document.getElementById('cfgRefresh').value = App.state.refreshRate || 15;
|
| 852 |
+
this.updatePresetsDropdown(); this.loadPreset("Default Flow");
|
| 853 |
},
|
| 854 |
close() { document.getElementById('configModal').style.display = 'none'; },
|
| 855 |
|