Overview

admin UTC

Feed

Provider
Frames
Reject Rate
Latency

WebSocket

State
Latency
Throughput

Strategy

ModeSHADOW
Master Score
Decision

Database

PostgreSQL
Valkey

Account

Status
Balance
Open Positions

XAUUSD

Bid
Ask
Spread: ( pts)

Market Session

Last tick:
UTC:

Signal Gate

Market Bias:
Reason:

Gate Status

SHADOW
Strategy
Confidence
Spread
Session
Freshness
Account
Risk
Kill Switch

XAUUSD · M5

● EMA 9● EMA 21● BB

Price

Bid
Ask: · Spread:

Master Score

NO TRADE
Bias: · Families:

Momentum

RSI: · MACD:

Volatility

ATR: · BBW:

Feed

"+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;i0.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'; 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.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 nowConnected'; } // 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();