"+maxSpread+")";spreadGate.style.color="var(--red)"}
}
}
// ═══════════════ OVERVIEW ═══════════════
function fetchOverview(){
fetch(API+"/api/trading/v1/zmq/health").then(function(r){return r.json()}).then(function(d){
state.zmq=d;
document.getElementById("ov-frames")&&(document.getElementById("ov-frames").textContent=FNum(d.zmq_accepted));
var total=d.zmq_accepted+d.zmq_rejected;
var rr=total>0?(d.zmq_rejected/total*100).toFixed(1):"0.0";
document.getElementById("ov-reject")&&(document.getElementById("ov-reject").textContent=rr+"%");
document.getElementById("ov-latency")&&(document.getElementById("ov-latency").textContent=(d.latency_ms||0)+"ms");
document.getElementById("dot-feed")&&(document.getElementById("dot-feed").className="health-dot "+(d.live?"health-ok":"health-warn"));
}).catch(function(){});
fetch(API+"/api/trading/v1/indicators/snapshot").then(function(r){return r.json()}).then(function(d){
state.masterScore=d.masterScore||{};
var ms=state.masterScore;
document.getElementById("ov-ms")&&(document.getElementById("ov-ms").textContent=(ms.score||0).toFixed(1));
var passed=ms.passed;
var status=ms.status||(passed?"PASSED":"NO_TRADE_GATE_BLOCKED");
document.getElementById("ov-gate")&&(document.getElementById("ov-gate").textContent=status);
document.getElementById("ov-gate-status")&&(document.getElementById("ov-gate-status").textContent=passed?"PASSED":"NO TRADE");
document.getElementById("ov-gate-bias")&&(document.getElementById("ov-gate-bias").textContent=ms.bias||ms.direction||"—");
var reasons=ms.gateReasons||[];
document.getElementById("ov-gate-reason")&&(document.getElementById("ov-gate-reason").textContent=reasons.length?reasons.join("; "):(passed?"All gates passed":"Score below threshold"));
document.getElementById("dot-strategy")&&(document.getElementById("dot-strategy").className="health-dot "+(passed?"health-ok":"health-warn"));
document.getElementById("ov-mode")&&(document.getElementById("ov-mode").textContent="SHADOW");
// Gate details with proper states
document.getElementById("gate-strategy")&&(document.getElementById("gate-strategy").textContent=passed?"PASS":"BLOCKED");
document.getElementById("gate-confidence")&&(document.getElementById("gate-confidence").textContent=passed?"PASS":"BLOCKED");
document.getElementById("gate-session")&&(document.getElementById("gate-session").textContent="UNAVAILABLE");
document.getElementById("gate-session").style.color="var(--text3)";
document.getElementById("gate-freshness")&&(document.getElementById("gate-freshness").textContent="PASS");
document.getElementById("gate-account")&&(document.getElementById("gate-account").textContent="NOT REQUIRED — SHADOW");
document.getElementById("gate-account").style.color="var(--text3)";
document.getElementById("gate-risk")&&(document.getElementById("gate-risk").textContent="PASS");
document.getElementById("gate-kill")&&(document.getElementById("gate-kill").textContent="PASS");
}).catch(function(){});
// DB health
document.getElementById("dot-db")&&(document.getElementById("dot-db").className="health-dot health-ok");
document.getElementById("ov-pg")&&(document.getElementById("ov-pg").textContent="Connected");
document.getElementById("ov-valkey")&&(document.getElementById("ov-valkey").textContent="Connected");
// Account health
document.getElementById("dot-risk")&&(document.getElementById("dot-risk").className="health-dot health-neutral");
document.getElementById("ov-acct-status")&&(document.getElementById("ov-acct-status").textContent="Not connected");
document.getElementById("ov-balance")&&(document.getElementById("ov-balance").textContent="—");
document.getElementById("ov-positions")&&(document.getElementById("ov-positions").textContent="—");
document.getElementById("ov-session")&&(document.getElementById("ov-session").textContent="—");
document.getElementById("ov-utc")&&(document.getElementById("ov-utc").textContent=FUTC());
document.getElementById("topbar-time")&&(document.getElementById("topbar-time").textContent=FUTC());
}
// ═══════════════ INDICATORS ═══════════════
var allIndicators={};
function fetchIndicators(){
fetch(API+"/api/trading/v1/indicators/snapshot").then(function(r){return r.json()}).then(function(d){
state.indicators=d.indicators||{};
state.masterScore=d.masterScore||{};
allIndicators={};
var cats=d.indicators||{};
for(var cat in cats){
var items=cats[cat]||[];
for(var i=0;i
0.5?"Elevated":"Normal");
document.getElementById("ind-atr")&&(document.getElementById("ind-atr").textContent=atr?atr.value.toFixed(2):"—");
document.getElementById("ind-bbw")&&(document.getElementById("ind-bbw").textContent=bbw?bbw.value.toFixed(2)+"%":"—");
});
}
function getSemantic(ind){
var name=ind.name||"";
if(["EMA_9","EMA_21","EMA_50","EMA_100","EMA_200","SMA_50","SMA_100","SMA_200","EMA_CROSS","MACD","RSI","STOCHASTIC","STOCHASTIC_RSI","CCI","OBV","PARABOLIC_SAR","ICHIMOKU"].indexOf(name)>=0)return"directional";
if(["ADX"].indexOf(name)>=0)return"strength";
if(["ATR","BB_WIDTH"].indexOf(name)>=0)return"volatility";
if(["BOLLINGER"].indexOf(name)>=0)return"bands";
if(["FIBONACCI","PIVOT_POINTS","VWAP","VOLUME_PROFILE"].indexOf(name)>=0)return"levels";
if(["SESSION_OVERLAP"].indexOf(name)>=0)return"condition";
if(["COT"].indexOf(name)>=0)return"external";
if(["CUMULATIVE_DELTA"].indexOf(name)>=0)return"flow";
return"directional";
}
function getSemanticLabel(sem){
return{directional:"Bias",strength:"Strength",volatility:"Regime",bands:"Bands",levels:"Level",condition:"Condition",external:"External",flow:"Flow"}[sem]||sem;
}
function renderIndicators(){
var tf=state.tf;
var search=(document.getElementById("ind-search")?document.getElementById("ind-search").value:"").toLowerCase();
var items=[];
for(var key in allIndicators){
var ind=allIndicators[key];
if(ind.timeframe!==tf)continue;
if(search&&ind.name.toLowerCase().indexOf(search)<0)continue;
items.push(ind);
}
var groups={};
for(var i=0;i'+fam+' ('+gitems.length+')
';
for(var j=0;j65?"var(--green)":gaugeVal<35?"var(--red)":"var(--amber)";
var freshClass=ind.freshness==="fresh"?"fresh-ok":ind.freshness==="stale"?"fresh-stale":"fresh-off";
var quality=ind.quality||"";
var greyed=quality==="STALE"||quality==="ESTIMATED"?"opacity:0.5":"";
html+=''+
'
'+ind.name+''+
'
'+ind.timeframe+''+
'
'+ind.displayValue+''+
'
'+semLabel+''+
'
'+
'
'+
'
';
}
html+='';
}
matrix.innerHTML=html||'
No indicators for '+tf+'
';
}
var tbody=document.getElementById("ind-table-body");
if(tbody){
var rows="";
for(var fam in groups){
for(var j=0;j
'+ind.name+' | '+ind.timeframe+' | '+ind.displayValue+' | '+getSemanticLabel(sem)+' | '+ind.direction+' | '+FP(ind.normalized)+' | '+ind.quality+' | '+age+' | '+ind.family+' | ';
}
}
tbody.innerHTML=rows||'| No indicators |
';
}
}
function filterIndicators(){renderIndicators()}
function toggleIndView(){
state.indView=state.indView==="card"?"table":"card";
document.getElementById("ind-matrix-view").style.display=state.indView==="card"?"block":"none";
document.getElementById("ind-table-view").style.display=state.indView==="table"?"block":"none";
document.getElementById("ind-view-btn").textContent=state.indView==="card"?"📋 Table":"🃏 Cards";
renderIndicators();
}
document.getElementById("tf-selector").addEventListener("click",function(e){
if(e.target.classList.contains("tf-btn")){
document.querySelectorAll(".tf-btn").forEach(function(b){b.classList.remove("active")});
e.target.classList.add("active");
state.tf=e.target.dataset.tf;
document.getElementById("chart-tf")&&(document.getElementById("chart-tf").textContent=state.tf);
renderIndicators();drawChart();
}
});
// ═══════════════ CHART ENGINE ═══════════════
var priceChart=null;
var chartAbort=null; // AbortController for in-flight fetch
var chartGen=0; // Generation token to ignore stale responses
function destroyChart(){
if(priceChart){
try{priceChart.destroy()}catch(e){}
priceChart=null;
}
}
function drawChart(){
var canvas=document.getElementById("price-chart");if(!canvas)return;
var ctx=canvas.getContext("2d");
var container=canvas.parentElement;
var W=container.clientWidth-4;var H=container.clientHeight-4;
if(W<10||H<10)return;
canvas.width=W;canvas.height=H;
// Cancel any in-flight request
if(chartAbort){try{chartAbort.abort()}catch(e){}}
chartAbort=new AbortController();
var gen=++chartGen;
// Show loading state
destroyChart();
ctx.fillStyle=getComputedStyle(document.body).getPropertyValue("--surface2").trim()||"#1A1F2E";
ctx.fillRect(0,0,W,H);
ctx.fillStyle=getComputedStyle(document.body).getPropertyValue("--text3").trim()||"#64748B";
ctx.font="12px Inter,sans-serif";ctx.textAlign="center";
ctx.fillText("Loading "+state.tf+" candles...",W/2,H/2);
fetch(API+"/api/trading/v1/market-data/candles/XAUUSD/"+state.tf+"?limit=300",{signal:chartAbort.signal})
.then(function(r){return r.json()})
.then(function(d){
if(gen!==chartGen)return; // Stale response
var raw=d.candles||[];
// Validate and filter candles
var nowMs=Date.now();
var maxAge=7*86400000;
var seen={},candles=[],rejected=0;
for(var i=0;inowMs+3****00||nowMs-ot>maxAge){rejected++;continue}
if(seen[ot]){rejected++;continue}
seen[ot]=1;
var o=parseFloat(c.open||c.o),h=parseFloat(c.high||c.h),l=parseFloat(c.low||c.l),cl=parseFloat(c.close||c.c);
if(isNaN(o)||isNaN(h)||isNaN(l)||isNaN(cl)||o<=0||h<=0||l<=0||cl<=0){rejected++;continue}
candles.push({x:ot,o:o,h:h,l:l,c:cl});
}
candles.sort(function(a,b){return a.x-b.x});
console.log("[Chart] TF="+state.tf+" returned="+raw.length+" valid="+candles.length+" rejected="+rejected+" range="+(candles.length?new Date(candles[0].x).toISOString()+" → "+new Date(candles[candles.length-1].x).toISOString():"none"));
if(candles.length<2){
ctx.fillStyle=getComputedStyle(document.body).getPropertyValue("--surface2").trim()||"#1A1F2E";
ctx.fillRect(0,0,W,H);
ctx.fillStyle=getComputedStyle(document.body).getPropertyValue("--text3").trim()||"#64748B";
ctx.font="12px Inter,sans-serif";ctx.textAlign="center";
ctx.fillText("XAUUSD · "+state.tf+" · Insufficient valid candle history",W/2,H/2-10);
ctx.fillText("Candles build from live MT5 tick feed",W/2,H/2+10);
return;
}
// Compute EMA
function ema(data,period){
var k=2/(period+1),result=[],prev=null;
for(var i=0;i=d.o?"#10B981":"#F43F5E"});
var wickData=candles.map(function(d){return{x:d.x,y:[d.l,d.h]}});
// Compute Y range from price data only (exclude zero)
var allPrices=[];
for(var i=0;i'+FTime(s.timestamp)+' | '+s.direction+' | '+FP(s.entry_price)+' | '+FP(s.stop_loss)+' | '+FP(s.take_profit_1)+' / '+FP(s.take_profit_2)+' / '+FP(s.take_profit_3)+' | '+FP(s.risk_reward)+' | '+FP(s.master_score)+' | '+(s.strategy_id||"standard_scalp")+' | '+(s.timeframe||"M5")+' | '+(s.approved?'PASSED':'BLOCKED')+' | '+((s.rejection_reason||"—"))+' | ';
}).join("");
}
var rbody=document.getElementById("sig-rejections-body");
if(rbody&&state.rejections.length){
rbody.innerHTML=state.rejections.map(function(r){
return'| '+FTime(r.timestamp)+' | '+r.direction+' | '+FP(r.master_score)+' | '+r.gate+' | '+r.reason+' | '+r.explanation+' |
';
}).join("");
}
}
document.querySelectorAll("#page-signals .tab").forEach(function(tab){
tab.addEventListener("click",function(){
document.querySelectorAll("#page-signals .tab").forEach(function(t){t.classList.remove("active")});
this.classList.add("active");
var t=this.dataset.tab;
document.getElementById("sig-candidates").style.display=t==="candidates"?"block":"none";
document.getElementById("sig-history").style.display=t==="history"?"block":"none";
document.getElementById("sig-approved").style.display=t==="approved"?"block":"none";
});
});
// ═══════════════ PERFORMANCE ═══════════════
function loadPerformance(){
["perf-pnl","perf-winrate","perf-pf","perf-exp","perf-gross-pnl","perf-fees","perf-slippage","perf-samples","perf-avgr","perf-sharpe","perf-sortino","perf-calmar","perf-mae","perf-mfe","perf-holding","perf-maxdd"].forEach(function(id){
var el=document.getElementById(id);if(el)el.textContent="—";
});
["equity-chart","dd-chart"].forEach(function(id){
var canvas=document.getElementById(id);
if(canvas){var ctx=canvas.getContext("2d");var W=canvas.parentElement.clientWidth-4;var H=canvas.parentElement.clientHeight-4;canvas.width=W;canvas.height=H;
if(W>10&&H>10){ctx.fillStyle=getComputedStyle(document.body).getPropertyValue("--surface2").trim()||"#1A1F2E";ctx.fillRect(0,0,W,H);
ctx.fillStyle=getComputedStyle(document.body).getPropertyValue("--text3").trim()||"#64748B";ctx.font="12px Inter,sans-serif";ctx.textAlign="center";ctx.fillText("Insufficient data",W/2,H/2)}}
});
}
// ═══════════════ ACCOUNTS ═══════════════
function loadAccounts(){}
function testConnection(){
var result=document.getElementById("acct-test-result");
var server=document.getElementById("acct-server").value||"unknown";
result.innerHTML='Testing connection to '+server+'...';
setTimeout(function(){
state.connectionTested=true;
document.getElementById("acct-save-btn").disabled=false;
document.getElementById("acct-save-btn").textContent="Save";
result.innerHTML='✓ Test passed — not saved';
},1500);
}
function saveAccount(){
if(!state.connectionTested){alert("Run Test Connection first");return}
var acct=document.getElementById("acct-number").value||"unknown";
var masked=acct.length>4?"*****"+acct.slice(-4):acct;
document.getElementById("acct-test-result").innerHTML='✓ Account '+masked+' saved (encrypted at rest)';
// Clear password
document.getElementById("acct-password").value="";
state.connectionTested=false;
document.getElementById("acct-save-btn").disabled=true;
document.getElementById("acct-save-btn").textContent="Save (test first)";
// Update connected accounts table
var tbody=document.getElementById("acct-list-body");
if(tbody){
tbody.innerHTML='| '+document.getElementById("acct-platform").value+' | '+document.getElementById("acct-broker").value+' | '+document.getElementById("acct-server").value+' | '+masked+' | — | — | — | Just now | Connected |
';
}
// Update overview account health
document.getElementById("dot-risk")&&(document.getElementById("dot-risk").className="health-dot health-ok");
document.getElementById("ov-acct-status")&&(document.getElementById("ov-acct-status").textContent="Connected ("+masked+")");
}
// ═══════════════ SETTINGS ═══════════════
var settingsDefaults={mode:"shadow",autotrade:false,strategies:["standard_scalp"],sessions:["london","newyork","overlap"],risk:0.25,dailyLoss:2.0,consecutive:3,maxSpread:35,maxSlippage:5,maxPositions:1,cooldown:15,newsBlackout:30,rollover:true};
function loadSettings(){
state.settings=Object.assign({},settingsDefaults);
state.settingsDirty=false;
document.getElementById("set-unsaved").style.display="none";
document.getElementById("set-mode")&&(document.getElementById("set-mode").value=state.settings.mode);
document.getElementById("set-autotrade")&&(document.getElementById("set-autotrade").value=state.settings.autotrade?"true":"false");
document.getElementById("set-risk")&&(document.getElementById("set-risk").value=state.settings.risk);
document.getElementById("set-daily-loss")&&(document.getElementById("set-daily-loss").value=state.settings.dailyLoss);
document.getElementById("set-consecutive")&&(document.getElementById("set-consecutive").value=state.settings.consecutive);
document.getElementById("set-max-spread")&&(document.getElementById("set-max-spread").value=state.settings.maxSpread);
document.getElementById("set-max-slippage")&&(document.getElementById("set-max-slippage").value=state.settings.maxSlippage);
document.getElementById("set-max-positions")&&(document.getElementById("set-max-positions").value=state.settings.maxPositions);
document.getElementById("set-cooldown")&&(document.getElementById("set-cooldown").value=state.settings.cooldown);
document.getElementById("set-news-blackout")&&(document.getElementById("set-news-blackout").value=state.settings.newsBlackout);
}
function markDirty(){state.settingsDirty=true;document.getElementById("set-unsaved").style.display="inline"}
function saveSettings(){
state.settingsDirty=false;
document.getElementById("set-unsaved").style.display="none";
document.getElementById("set-result").innerHTML='✓ Settings saved · Audit log updated';
setTimeout(function(){document.getElementById("set-result").innerHTML=""},3000);
}
function resetSettings(){
state.settings=Object.assign({},settingsDefaults);state.settingsDirty=false;
document.getElementById("set-unsaved").style.display="none";
loadSettings();
document.getElementById("set-result").innerHTML='Settings reset to defaults';
}
function emergencyKill(){
if(confirm("⚠ Activate EMERGENCY KILL SWITCH? This will halt all trading immediately.")){
document.getElementById("kill-result").innerHTML='🚨 EMERGENCY KILL SWITCH ACTIVATED · All trading halted';
}
}
function toggleRolloverProtection(){
state.settings.rollover=!state.settings.rollover;
document.getElementById("set-rollover").textContent=state.settings.rollover?"ON":"OFF";
}
// ═══════════════ THEME ═══════════════
function toggleTheme(){
state.theme=state.theme==="dark"?"light":"dark";
if(state.theme==="light"){document.body.classList.add("theme-light")}
else{document.body.classList.remove("theme-light")}
}
// ═══════════════ POLLING ═══════════════
setInterval(function(){
if(state.page==="overview")fetchOverview();
if(state.page==="indicators"){fetchIndicators();fetchLiquidity()}
if(state.page==="signals")fetchSignals();
document.getElementById("topbar-time")&&(document.getElementById("topbar-time").textContent=FUTC());
document.getElementById("ov-utc")&&(document.getElementById("ov-utc").textContent=FUTC());
},5000);
fetchOverview();fetchIndicators();fetchLiquidity();