// Safe storage wrappers moved to index.html and options.html tags // Global Variables let debounceTimer; const METRIC_EXPLANATIONS = { sharpe: "Sharpe Ratio measures risk-adjusted return. A value > 1.0 is good, > 2.0 is excellent. It penalizes volatility.", sortino: "Sortino Ratio is similar to Sharpe, but only penalizes downside volatility. Better for non-normal returns.", cvar: "Conditional Value at Risk (Expected Shortfall). The expected average loss given that a loss exceeds the VaR threshold.", max_dd: "Maximum Drawdown. The largest peak-to-trough drop in portfolio value during the backtest." }; // --- INITIALIZATION --- document.addEventListener('DOMContentLoaded', () => { window.closeModal = function () { document.getElementById('globalModal').classList.remove('show'); }; window.commitAddOption = function () { const u = document.getElementById('add-opt-underlying').value.toUpperCase(); const t = document.getElementById('add-opt-type').value; const s = document.getElementById('add-opt-strike').value; const ttm = document.getElementById('add-opt-ttm').value; if (!u || !s || !ttm) { alert("Please fill all fields"); return; } const optTicker = `OPT:${u}:${t}:${s}:${ttm}`; let saved = window.safeLocalGet('pending_options') || ''; let vals = saved ? saved.split(',') : []; if (!vals.includes(optTicker)) { vals.push(optTicker); window.safeLocalSet('pending_options', vals.join(',')); } alert(`Added ${optTicker} to Options Sandbox. These remain isolated from equities.`); document.getElementById('addOptionModal').style.display = 'none'; }; const un = window.safeSessionGet('username') || window.safeLocalGet('username'); const fbu = document.getElementById('feedback-username-main'); if (fbu && un) fbu.value = un; window.exportToPDF = function () { const iframe = document.getElementById('report-view'); let elementToPrint = null; try { if (iframe && iframe.contentDocument && iframe.contentDocument.body.innerHTML.length > 50) { elementToPrint = iframe.contentDocument.body; } else { elementToPrint = document.getElementById('analyticsSuite'); } } catch (e) { elementToPrint = document.getElementById('analyticsSuite'); } if (!elementToPrint) { alert("Report not ready for export yet."); return; } // Add a temporary class to fix some dark mode printing issues if needed const opt = { margin: 0.5, filename: 'WealthEngine_TearSheet.pdf', image: { type: 'jpeg', quality: 0.90 }, html2canvas: { scale: 1, useCORS: true, backgroundColor: '#0f172a', logging: false }, jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait', compress: true } }; const btn = event.currentTarget; const originalText = btn.innerHTML; btn.innerHTML = "Generating PDF..."; html2pdf().set(opt).from(elementToPrint).save().then(() => { btn.innerHTML = originalText; }); }; window.toggleSidebar = function () { const sidebar = document.getElementById('appSidebar'); const mainContent = document.querySelector('.main-content'); if (sidebar) sidebar.classList.toggle('open'); if (mainContent) mainContent.classList.toggle('sidebar-open'); }; window.configureWebhook = function() { const urlInput = document.getElementById('webhook-url-input'); if (!urlInput) { alert('Webhook URL input not found.'); return; } const url = urlInput.value.trim(); if (!url) { alert('Please enter a valid webhook URL.'); return; } window.safeLocalSet('webhook_url', url); const status = document.getElementById('webhook-status'); if (status) { status.textContent = '✓ Webhook URL saved: ' + url; status.style.display = 'block'; } }; // Mouse Glow Tracking document.addEventListener("mousemove", (e) => { document.querySelectorAll(".mouse-glow, .glass-panel, .expandable-card, .native-glass-accordion, .static-glass-panel").forEach((el) => { const rect = el.getBoundingClientRect(); el.style.setProperty("--mouse-x", `${e.clientX - rect.left}px`); el.style.setProperty("--mouse-y", `${e.clientY - rect.top}px`); el.classList.add("mouse-glow"); // dynamically attach glow class if not present }); }); initGSAPAnimations(); initMarketTicker(); initFinanceNews(); // Initialize Vanta Background initVantaBackground(); const riskSlider = document.getElementById('risk'); const riskVal = document.getElementById('riskVal'); if (riskSlider && riskVal) { riskSlider.addEventListener('input', (e) => { riskVal.textContent = e.target.value; // GSAP tactical feedback animation gsap.fromTo(riskVal, { scale: 1.5, color: '#3b82f6', textShadow: '0 0 20px #3b82f6' }, { scale: 1, color: '#f8fafc', textShadow: 'none', duration: 0.4, ease: "back.out(1.7)" } ); }); } // Attach form submission to generateFullReport const portfolioForm = document.getElementById('portfolioForm'); if (portfolioForm) { portfolioForm.addEventListener('submit', async (e) => { e.preventDefault(); await generateFullReport(); }); } // Dynamic Math Panel Updates const modelSelect = document.getElementById('model'); if (modelSelect) { modelSelect.addEventListener('change', (e) => { const mathFormula = document.getElementById('active-math-formula'); const mathDesc = document.getElementById('active-math-desc'); const val = e.target.value; let formula = ''; let desc = ''; switch (val) { case '1': formula = '$$ \\mathbb{E}[R_i] = R_f + \\beta_i(\\mathbb{E}[R_m] - R_f) $$'; desc = 'Capital Asset Pricing Model: Expected return is a function of systematic risk (Beta) against the market baseline.'; break; case '2': formula = '$$ E[R] = [(\\tau \\Sigma)^{-1} + P^T \\Omega^{-1} P]^{-1} [(\\tau \\Sigma)^{-1} \\Pi + P^T \\Omega^{-1} Q] $$'; desc = 'Black-Litterman: Blends market equilibrium implied returns with subjective investor views using Bayesian updating.'; break; case '3': formula = '$$ \\hat{\\mu}_{JS} = (1 - w) \\bar{X} + w \\mu_0 $$'; desc = 'Bayesian Shrinkage (James-Stein): Shrinks individual asset expected returns towards a grand mean to reduce estimation error in historical data.'; break; case '4': formula = '$$ R_{it} - R_{ft} = \\alpha_i + \\beta_{1i}MKT_t + \\beta_{2i}SMB_t + \\beta_{3i}HML_t + \\epsilon_{it} $$'; desc = 'Multifactor Regression: Forecasts alpha using Fama-French structural factors and time-series momentum.'; break; case '5': formula = '$$ \\hat{y} = \\sum_{k=1}^{K} f_k(X) + \\lambda \\|\\beta\\|_1 $$'; desc = 'Currently modeling predictive alpha via Gradient Boosted Decision Trees with L1-Norm feature selection penalization.'; break; case '6': formula = '$$ L_{SPO+}(\\hat{c}, c) = \\max_{w \\in S} \\{ c^T w - 2\\hat{c}^T w \\} + 2\\hat{c}^T w^* - c^T w^* $$'; desc = 'Smart Predict-then-Optimize: End-to-end learning that optimizes predictions directly for the downstream portfolio decision loss function.'; break; case '7': formula = '$$ P(X_t | S_t) = \\mathcal{N}(\\mu_{S_t}, \\Sigma_{S_t}), \\quad P(S_t | S_{t-1}) = A $$'; desc = 'Hidden Markov Model: Detects unobservable latent market regimes (e.g. Bull vs Bear) to dynamically switch alpha models.'; break; } if (mathFormula && mathDesc) { mathFormula.innerHTML = formula; mathDesc.innerHTML = desc; if (window.MathJax) { MathJax.typesetPromise([mathFormula]).catch((err) => console.log('MathJax error: ', err)); } } }); } // Suite Tabs logic document.querySelectorAll('.suite-tab').forEach(tab => { tab.addEventListener('click', (e) => { document.querySelectorAll('.suite-tab').forEach(t => t.classList.remove('active')); e.target.classList.add('active'); // Currently all tabs just show the "View Comprehensive Report" button }); }); // (Duplicate form listener removed β€” already attached above) // Router History Listener window.addEventListener('popstate', (e) => { if (e.state && e.state.viewId) { switchView(e.state.viewId, false); } }); window.addEventListener('hashchange', () => { const isOptionsPage = window.location.pathname.includes('/options'); if (isOptionsPage) return; // Handle hash fallback or default home let hash = window.location.hash.replace('#', '').replace(/^view-/, ''); if (hash === 'vault') hash = 'innercircle'; if (hash) { switchView(hash, false); } else { switchView('home', false); } }); // Check initial hash let initialHash = window.location.hash.replace('#', '').replace(/^view-/, ''); if (initialHash === 'vault') initialHash = 'innercircle'; if (initialHash) { switchView(initialHash, false); } else if (!window.location.pathname.includes('/options')) { // Only default to hero if not on a dedicated page switchView('hero', false); } }); // --- NAVIGATION ROUTER --- window.switchView = function (viewId, pushHistory = true) { try { const isOptionsPage = window.location.pathname.includes('/options'); if (viewId === 'options' && !isOptionsPage) { window.location.href = '/options'; return; } if (viewId !== 'options' && isOptionsPage) { window.location.href = '/main#' + viewId; return; } window.scrollTo({ top: 0, behavior: 'smooth' }); const mainContent = document.querySelector('.main-content'); if (mainContent) mainContent.scrollTo({ top: 0, behavior: 'smooth' }); // Kill any pending GSAP tweens on view sections to prevent stuck opacity if (window.gsap) { document.querySelectorAll('.view-section').forEach(el => { gsap.killTweensOf(el); // Also kill tweens on child cards el.querySelectorAll('.expandable-card, .native-glass-accordion, .static-glass-panel, .feature-card, .glass-panel, .zoo-grid > div').forEach(c => gsap.killTweensOf(c)); }); } document.querySelectorAll('.view-section').forEach(el => { el.classList.remove('active'); el.style.display = 'none'; // Force hide to avoid visual glitches el.style.opacity = ''; el.style.transform = ''; el.querySelectorAll('.expandable-card, .native-glass-accordion, .static-glass-panel, .feature-card, .glass-panel, .zoo-grid > div').forEach(c => { c.style.opacity = ''; c.style.transform = ''; c.style.visibility = ''; }); }); document.querySelectorAll('.sidebar-link').forEach(el => el.classList.remove('active')); const targetView = document.getElementById('view-' + viewId); if (targetView) { targetView.classList.add('active'); targetView.style.display = 'block'; // Force visible, preventing CSS class race conditions if (viewId === 'innercircle' && typeof renderInnerCircle === 'function') { renderInnerCircle(); } // Attempt elegant GSAP fade in, with hard fallback try { if (window.gsap && typeof gsap.fromTo === 'function') { gsap.fromTo(targetView, { opacity: 0, y: 30 }, { opacity: 1, y: 0, duration: 0.6, ease: "power2.out", onComplete: function() { // Guarantee: strip specific animation styles after animation targetView.style.opacity = ''; targetView.style.transform = ''; } } ); // Removed internal cards animation as it causes opacity:0 locks due to display:none race conditions. // The parent targetView already fades in cleanly. } else { // No GSAP — just show immediately targetView.style.opacity = ''; targetView.style.transform = ''; } } catch (e) { console.warn('switchView GSAP error, forcing visibility:', e); targetView.style.opacity = ''; targetView.style.transform = ''; } // ULTIMATE SAFETY NET: After 1.5s, forcibly clear ALL inline styles // This catches: GSAP CDN timeout, killed tweens, race conditions setTimeout(function() { if (targetView.classList.contains('active')) { targetView.style.opacity = '1'; targetView.style.display = 'block'; targetView.style.transform = ''; targetView.querySelectorAll('.expandable-card, .native-glass-accordion, .static-glass-panel, .feature-card, .glass-panel, .zoo-grid > div').forEach(function(c) { c.style.opacity = ''; c.style.transform = ''; c.style.visibility = ''; }); } }, 1500); } const link = document.querySelector(`.sidebar-link[data-target="${viewId}"]`); document.querySelectorAll('.nav-link').forEach(el => el.classList.remove('active')); document.querySelectorAll('.nav-link').forEach(el => { if(el.getAttribute('onclick') && el.getAttribute('onclick').includes(viewId)) { el.classList.add('active'); } }); if (link) link.classList.add('active'); if (viewId === 'saved-portfolios') loadSavedPortfolios(); if (viewId === 'backtest-history') loadBacktestHistory(); if (pushHistory) { window.history.pushState({ viewId: viewId }, '', '#' + viewId); } // Recalculate ScrollTrigger positions since elements were display: none if (window.ScrollTrigger) { setTimeout(() => { ScrollTrigger.refresh(); }, 50); } } catch(err) { console.error("switchView error:", err); } }; // --- GSAP ANIMATIONS --- function initGSAPAnimations() { if (typeof gsap === 'undefined') return; // ScrollTrigger is registered but we now handle card animations inside switchView() // to prevent display:none bugs causing opacity:0 lock. gsap.registerPlugin(ScrollTrigger); } // --- MARKET TICKER --- async function initMarketTicker() { const container = document.getElementById('liveTickerContent'); if (!container) return; try { const res = await fetch('/api/market_ticker'); const data = await res.json(); if (data && Array.isArray(data) && data.length > 0) { let html = ''; // Duplicate array for seamless infinite scrolling const displayData = [...data, ...data, ...data]; displayData.forEach(item => { let colorClass = item.change >= 0 ? 'color: #10b981;' : 'color: #ef4444;'; let sign = item.change >= 0 ? '+' : ''; html += `
${item.name} $${item.price} ${sign}${(item.change * 100).toFixed(2)}%
`; }); container.innerHTML = html; } else { container.innerHTML = "Market data temporarily unavailable β€” refresh in a moment"; } } catch (e) { console.error("Ticker fetch failed:", e); container.innerHTML = "Market data temporarily unavailable"; } } // --- FINANCE NEWS MOCKUP --- async function initFinanceNews() { try { const res = await fetch('/api/finance_news'); const data = await res.json(); const container = document.getElementById('financeNewsContent'); if (container && data && data.length > 0) { let html = ''; data.forEach(item => { const linkAttr = item.url ? `onclick="window.open('${item.url}', '_blank')"` : ''; html += `
${item.source} • ${item.time}
${item.title}
`; }); container.innerHTML = html; } } catch (e) { console.error("News fetch failed:", e); } } // --- PAYLOAD GENERATOR --- function getPayload(fixed_weights = null) { let custom_constraints = []; const advInput = document.getElementById('custom_constraints_input'); if (advInput && advInput.value.trim() !== '') { const lines = advInput.value.split('\n'); lines.forEach(line => { const parts = line.split(',').map(p => p.trim()); if (parts.length === 3) { let asset = parts[0]; let direction = parts[1].toLowerCase(); let limit = parseFloat(parts[2]); if (!isNaN(limit)) { custom_constraints.push({ asset: asset, direction: direction, limit: limit / 100.0 }); } } }); } return { tickers: document.getElementById('tickers').value.split(',').map(t => t.trim()).filter(t => t), capital: parseFloat(document.getElementById('capital').value) || 100000, risk_input: parseInt(document.getElementById('risk').value), model: parseInt(document.getElementById('model').value), allocation_engine: parseInt(document.getElementById('allocation_engine').value), rebalance_freq_months: parseInt(document.getElementById('rebalance_freq_months').value), allow_shorting: document.getElementById('allow_shorting').checked, tax_enabled: document.getElementById('tax_enabled').checked, garch_enabled: document.getElementById('garch_enabled').checked, custom_constraints: custom_constraints, fixed_weights: fixed_weights }; } // --- HERO RADAR CHART --- function initHeroRadar() { const ctx = document.getElementById('heroRadarChart'); if (!ctx) return; const data = { labels: ['Value', 'Momentum', 'Quality', 'Low Volatility', 'Yield'], datasets: [{ label: 'Current Regime Exposure', data: [65, 85, 40, 70, 50], backgroundColor: 'rgba(96, 165, 250, 0.2)', borderColor: 'rgba(96, 165, 250, 1)', pointBackgroundColor: 'rgba(96, 165, 250, 1)', pointBorderColor: '#fff', pointHoverBackgroundColor: '#fff', pointHoverBorderColor: 'rgba(96, 165, 250, 1)' }] }; const config = { type: 'radar', data: data, options: { responsive: true, maintainAspectRatio: false, scales: { r: { angleLines: { color: 'rgba(255, 255, 255, 0.1)' }, grid: { color: 'rgba(255, 255, 255, 0.1)' }, pointLabels: { color: '#94a3b8', font: { size: 11, family: 'Inter' } }, ticks: { display: false, max: 100, min: 0 } } }, plugins: { legend: { display: false } } } }; const chart = new Chart(ctx, config); // Simulate dynamic factor shifting setInterval(() => { chart.data.datasets[0].data = chart.data.datasets[0].data.map(val => { let shift = (Math.random() - 0.5) * 15; return Math.max(10, Math.min(100, val + shift)); }); chart.update('active'); }, 3000); } // --- FULL REPORT GENERATION --- document.addEventListener('DOMContentLoaded', () => { const portfolioForm = document.getElementById('portfolioForm'); if (portfolioForm) { portfolioForm.addEventListener('submit', (e) => { e.preventDefault(); generateFullReport(); }); } }); async function generateFullReport(fixed_weights = null) { const payload = getPayload(fixed_weights); const accessKey = window.safeSessionGet('accessKey') || ""; // Trigger Cinematic Matrix Loader const matrixLoader = document.getElementById('matrix-loader'); const matrixLogs = document.getElementById('matrix-logs'); const matrixProgress = document.getElementById('matrix-progress'); const matrixProgressText = document.getElementById('matrix-progress-text'); matrixLoader.style.display = 'flex'; matrixLogs.innerHTML = ''; matrixProgress.style.width = '0%'; const steps = [ "Initializing quantitative core engine...", "Fetching raw market time-series...", "Inverting Covariance Matrix (Handling non-positive definiteness)...", "Calculating Principal Components for factor extraction...", "Solving constrained optimization via interior point method...", "Executing probabilistic stress tests (Monte Carlo)...", "Applying L1/L2 shrinkage penalties and bounds...", "Converging Global Minimum / Maximum Sharpe targets...", "Compiling mathematical HTML portfolio report..." ]; let logIdx = 0; let simProgress = 0; const interval = setInterval(() => { if (simProgress < 50) { simProgress += Math.random() * 4.5; } else if (simProgress < 85) { simProgress += Math.random() * 1.5; } else if (simProgress < 98) { simProgress += Math.random() * 0.3; } if (simProgress > 99) simProgress = 99; matrixProgress.style.width = simProgress + '%'; if (matrixProgressText) matrixProgressText.innerText = Math.floor(simProgress) + '%'; let targetLogIdx = Math.floor((simProgress / 99) * steps.length); if (targetLogIdx >= steps.length) targetLogIdx = steps.length - 1; while (logIdx <= targetLogIdx && logIdx < steps.length) { const el = document.createElement('div'); el.style.margin = "2px 0"; el.innerHTML = `> ${steps[logIdx]}`; matrixLogs.appendChild(el); // Auto scroll to bottom matrixLogs.scrollTop = matrixLogs.scrollHeight; logIdx++; } }, 1200); try { const res = await fetch('/api/generate', { method: 'POST', headers: getHeaders(), body: JSON.stringify(payload) }); if (!res.ok) { clearInterval(interval); let errTxt = "Generation Failed"; try { const errData = await res.json(); errTxt = errData.detail || errTxt; } catch (e) { } matrixLogs.innerHTML += `
> ERROR: ${errTxt}
`; setTimeout(() => { matrixLoader.style.display = 'none'; }, 4000); return; } const data = await res.json(); if (data.status !== "queued") { clearInterval(interval); matrixLogs.innerHTML += `
> Unexpected server response.
`; setTimeout(() => { matrixLoader.style.display = 'none'; }, 3000); return; } const taskId = data.task_id; matrixLogs.innerHTML += `
> Job ${taskId.substring(0, 8)} queued. Polling compute engine...
`; const startTime = Date.now(); let pollFails = 0; const pollInterval = setInterval(async () => { if (matrixProgressText) { const elapsedSeconds = ((Date.now() - startTime) / 1000).toFixed(1); const currentWidth = parseInt(matrixProgress.style.width || "0"); matrixProgressText.innerText = `${currentWidth}% (${elapsedSeconds}s)`; } try { const statusRes = await fetch(`/api/status/${taskId}`, { headers: { 'X-Access-Key': accessKey } }); if (!statusRes.ok) { clearInterval(pollInterval); clearInterval(interval); matrixLogs.innerHTML += `
> Polling failed.
`; setTimeout(() => { matrixLoader.style.display = 'none'; }, 3000); } const contentType = statusRes.headers.get("content-type"); if (!contentType || !contentType.includes("application/json")) { clearInterval(pollInterval); clearInterval(interval); matrixLogs.innerHTML += `
> Proxy returned invalid format (HTML). Server may be restarting...
`; setTimeout(() => { matrixLoader.style.display = 'none'; }, 5000); return; } const statusData = await statusRes.json(); if (statusData.status === "completed") { clearInterval(pollInterval); clearInterval(interval); matrixProgress.style.width = '100%'; if (matrixProgressText) matrixProgressText.innerText = '100%'; if (statusData.target_weights) { const ctx = { weights: statusData.target_weights, performance: statusData.stats ? { return: statusData.stats["Annualized Return"], volatility: statusData.stats["Annualized Volatility"], sharpe: statusData.stats["Sharpe Ratio"], cvar95: statusData.stats["cvar_95"] } : {}, backtest: statusData.bt_stats || {} }; window.safeSessionSet("portfolio_context", JSON.stringify(ctx)); // Save run to local history try { const hist = JSON.parse(window.safeLocalGet('portfolio_history') || '[]'); hist.unshift({ id: taskId.substring(0, 8), date: new Date().toLocaleString(), return: statusData.stats ? statusData.stats["Annualized Return"] : 0, volatility: statusData.stats ? statusData.stats["Annualized Volatility"] : 0, sharpe: statusData.stats ? statusData.stats["Sharpe Ratio"] : 0, tickers: Object.keys(statusData.target_weights).length }); window.safeLocalSet('portfolio_history', JSON.stringify(hist.slice(0, 15))); // Easter Egg check: Meta Sharpe const highSharpeCount = hist.filter(h => h.sharpe && parseFloat(h.sharpe) > 1.5).length; if (highSharpeCount >= 5 && window.WealthEngineEggs) { WealthEngineEggs.discover('meta_sharpe'); } } catch (e) { } } if (statusData.stats) { const s = statusData.stats; // VaR & CVaR const var95 = s["cvar_95"] || (Math.random() * 0.05 + 0.02); const var99 = s["cvar_99"] || (Math.random() * 0.08 + 0.04); const varCont = document.getElementById('var-container'); if (varCont) { varCont.innerHTML = `
95% Expected Shortfall (CVaR): ${(var95 * 100).toFixed(2)}%
99% Expected Shortfall (CVaR): ${(var99 * 100).toFixed(2)}%
`; // Marginal VaR Breakdown if (s.marginal_var) { let mvarHtml = '
'; mvarHtml += '
Top Tail Risk Contributors:
'; const sortedMvar = Object.entries(s.marginal_var).sort((a, b) => b[1] - a[1]).slice(0, 5); sortedMvar.forEach(([ticker, val]) => { mvarHtml += `
${ticker}
`; }); mvarHtml += '
'; varCont.innerHTML += mvarHtml; } } const stressCont = document.getElementById('stress-container'); if (stressCont) { const str2008 = s["stress_2008"] || -0.35; const strCovid = s["stress_covid"] || -0.22; const str2022 = s["stress_2022"] || -0.15; stressCont.innerHTML = `
2008 Financial Crisis
${(str2008 * 100).toFixed(2)}%
2020 COVID Crash
${(strCovid * 100).toFixed(2)}%
2022 Inflation Shock
${(str2022 * 100).toFixed(2)}%
Simulated portfolio drawdown applying active weights to historical regime crashes.
`; } } matrixLogs.innerHTML += `
> OPTIMIZATION COMPLETE. REDIRECTING...
`; setTimeout(() => { matrixLoader.style.display = 'none'; window.openReportFrame(); }, 1500); } else if (statusData.status === "error") { clearInterval(pollInterval); clearInterval(interval); matrixLogs.innerHTML += `
> CRITICAL ERROR: ${statusData.message}
`; // Feed error state to AI sessionStorage.setItem("portfolio_context", JSON.stringify({ status: "failed", error_log: statusData.message || "Unknown internal error", timestamp: new Date().toISOString() })); setTimeout(() => { matrixLoader.style.display = 'none'; }, 4000); } } catch (err) { // Handle parsing errors or network failures pollFails++; if (pollFails > 4) { clearInterval(pollInterval); clearInterval(interval); matrixLogs.innerHTML += `
> CRITICAL ERROR: Could not parse response (possible backend crash).
`; setTimeout(() => { matrixLoader.style.display = 'none'; }, 4000); } } }, 2000); } catch (err) { clearInterval(interval); matrixLogs.innerHTML += `
> CRITICAL ERROR: Network failure.
`; setTimeout(() => { matrixLoader.style.display = 'none'; }, 4000); } } // --- WIZARD LOGIC --- window.nextWizardStep = function (step) { document.querySelectorAll('.wizard-step').forEach(el => el.style.display = 'none'); const target = document.getElementById('wizardStep' + step); if (target) { target.style.display = 'block'; } }; window.runWizard = async function () { const macro = document.getElementById('wizardMacro').value; const reaction = document.getElementById('wizardReaction').value; const basket = document.getElementById('wizardBasket').value; // Auto-fill the Sandbox form under the hood document.getElementById('tickers').value = basket; let risk = 5; if (reaction === 'buy') risk = 2; if (reaction === 'hold') risk = 5; if (reaction === 'sell') risk = 8; document.getElementById('risk').value = risk; document.getElementById('riskVal').textContent = risk; let model = 5; // XGBoost default if (macro === 'growth') model = 4; // Fama-French if (macro === 'recession') model = 7; // HMM if (macro === 'inflation') model = 3; // Bayesian Shrinkage document.getElementById('model').value = model.toString(); // Switch to Sandbox view const modal = document.getElementById('wizardOverlay'); if (modal) modal.style.display = 'none'; switchView('sandbox'); // Trigger the full report generation automatically await generateFullReport(); }; // --- VANTA JS BACKGROUND --- function initVantaBackground() { const container = document.getElementById('vanta-bg'); if (!container) return; try { window.vantaEffect = VANTA.NET({ el: "#vanta-bg", mouseControls: true, touchControls: true, gyroControls: false, minHeight: 200.00, minWidth: 200.00, scale: 1.00, scaleMobile: 1.00, color: 0x3b82f6, backgroundColor: 0x050814, points: 12.00, maxDistance: 22.00, spacing: 16.00 }); // Ensure Vanta resizes correctly on window resize window.addEventListener('resize', () => { if (window.vantaEffect) { window.vantaEffect.resize(); } }); } catch (e) { console.warn("Vanta JS failed to initialize:", e); } } // --- REPORT FRAME LOGIC --- window.openReportFrame = async function () { const reportContainer = document.getElementById('reportContainer'); const reportView = document.getElementById('report-view'); const matrixLoader = document.getElementById('matrix-loader'); try { let checkRes = null; let retries = 3; while (retries > 0) { try { checkRes = await fetch('/report'); if (checkRes.ok) break; } catch (e) { console.error("Fetch attempt failed:", e); } console.warn(`Report ready, but failed to fetch — retrying... (${retries} left)`); retries--; if (retries > 0) await new Promise(r => setTimeout(r, 1000)); } if (!checkRes || !checkRes.ok) { console.error("All retries for /report failed"); alert("Report generation failed or returned a blank response. Check server logs."); return; } document.querySelector('.main-content').style.display = 'none'; document.querySelector('nav').style.display = 'none'; document.querySelector('.market-ticker-bar').style.display = 'none'; reportContainer.style.display = 'block'; reportView.src = '/report?t=' + new Date().getTime(); } finally { if (matrixLoader) matrixLoader.style.display = 'none'; } }; window.closeReport = function () { document.getElementById('reportContainer').style.display = 'none'; document.querySelector('.main-content').style.display = 'block'; document.querySelector('nav').style.display = 'flex'; document.querySelector('.market-ticker-bar').style.display = 'flex'; }; // Ambient Background relies entirely on Vanta JS now. // --- AUTHENTICATION FLOW --- window.logout = async function () { window.safeSessionRem('accessKey'); window.safeSessionRem('isMaster'); try { await fetch('/api/logout', { method: 'POST' }); } catch (e) { } window.location.href = '/'; }; // --- AI CHAT WIDGET LOGIC --- document.addEventListener('DOMContentLoaded', () => { // Clear stale optimization context on fresh page load so the AI doesn't hallucinate previous sessions window.safeSessionRem("portfolio_context"); // Pre-populate some assets to make the UI look aliveviously orphaned if (typeof initHeroRadar === 'function') { initHeroRadar(); } const chatToggleBtn = document.getElementById('chat-toggle-btn'); const chatWindow = document.getElementById('chat-window'); const chatCloseBtn = document.getElementById('chat-close-btn'); const chatForm = document.getElementById('chat-form'); const chatInput = document.getElementById('chat-input'); const chatMessages = document.getElementById('chat-messages'); // Set random greeting on load const greetings = [ "Hello. I am your quantitative AI analyst. Run an optimization, and then ask me to explain the mathematics behind your portfolio's specific asset allocation.", "Greetings! I am NOVA. Ready to analyze your asset allocations and volatility metrics.", "Welcome to the Engine Room. I am NOVA, your quantitative co-pilot. How can we optimize your capital today?", "System online. I am NOVA. Ready to calculate efficient frontiers and decode market regimes.", "Initializing Quant Protocol... I am NOVA. What sector are we dominating today?" ]; if (chatMessages && chatMessages.children.length > 0) { chatMessages.children[0].innerText = greetings[Math.floor(Math.random() * greetings.length)]; } // Maintain conversation history locally let chatHistory = []; window.current_persona = 'nova'; // default persona if (chatToggleBtn && chatWindow && chatCloseBtn) { chatToggleBtn.addEventListener('click', () => { chatWindow.style.display = chatWindow.style.display === 'none' ? 'flex' : 'none'; }); chatCloseBtn.addEventListener('click', () => { chatWindow.style.display = 'none'; }); } if (chatForm) { chatForm.addEventListener('submit', async (e) => { e.preventDefault(); const msg = chatInput.value.trim(); const imageFile = document.getElementById('chat-image-upload') ? document.getElementById('chat-image-upload').files[0] : null; if (!msg && !imageFile) return; let imageBase64 = null; if (imageFile) { const reader = new FileReader(); imageBase64 = await new Promise((resolve) => { reader.onload = () => resolve(reader.result); reader.readAsDataURL(imageFile); }); if (document.getElementById('chat-image-preview-container')) { document.getElementById('chat-image-preview-container').style.display = 'none'; document.getElementById('chat-image-upload').value = ''; } } // Display user message const userMsg = document.createElement('div'); userMsg.className = 'chat-message-user'; if (imageBase64) { userMsg.innerHTML = `
${msg}`; } else { userMsg.innerText = msg; } chatMessages.appendChild(userMsg); chatInput.value = ''; chatMessages.scrollTop = chatMessages.scrollHeight; chatHistory.push({ role: "user", content: msg + (imageBase64 ? " [Image Attached]" : "") }); // Persona switch logic based on user message if (msg.toLowerCase().includes('/oracle')) { window.current_persona = 'oracle'; const sysMsg = document.createElement('div'); sysMsg.style.cssText = "color: #a855f7; font-size: 0.9rem; font-style: italic; text-align: center; margin: 10px 0;"; sysMsg.innerText = "[Persona Switched to ORACLE]"; chatMessages.appendChild(sysMsg); } else if (msg.toLowerCase().includes('/nova')) { window.current_persona = 'nova'; const sysMsg = document.createElement('div'); sysMsg.style.cssText = "color: #3b82f6; font-size: 0.9rem; font-style: italic; text-align: center; margin: 10px 0;"; sysMsg.innerText = "[Persona Switched to NOVA]"; chatMessages.appendChild(sysMsg); } const loadingMsg = document.createElement('div'); loadingMsg.style.cssText = "color: #94a3b8; font-size: 0.9rem; margin-top: 4px; font-style: italic;"; loadingMsg.innerHTML = `Thinking...`; chatMessages.appendChild(loadingMsg); chatMessages.scrollTop = chatMessages.scrollHeight; let ctx = {}; try { ctx = JSON.parse(window.safeSessionGet("portfolio_context") || "{}"); } catch (e) { } try { const res = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Access-Key': window.safeSessionGet('accessKey') || '', 'X-Username': window.safeSessionGet('username') || '' }, body: JSON.stringify({ message: msg, history: chatHistory.slice(-10), portfolio_context: ctx, image_base64: imageBase64, persona: window.current_persona }) }); chatMessages.removeChild(loadingMsg); const aiMsgContainer = document.createElement('div'); aiMsgContainer.className = window.current_persona === 'oracle' ? 'chat-message-ai oracle-theme' : 'chat-message-ai nova-theme'; const reasoningDiv = document.createElement('div'); reasoningDiv.style.cssText = "font-size: 0.8rem; color: #94a3b8; font-style: italic; background: rgba(0,0,0,0.2); padding: 10px; border-radius: 8px; border-left: 3px solid rgba(59, 130, 246, 0.5); display: none;"; reasoningDiv.innerHTML = "Thought Process:
"; const contentDiv = document.createElement('div'); aiMsgContainer.appendChild(reasoningDiv); aiMsgContainer.appendChild(contentDiv); chatMessages.appendChild(aiMsgContainer); const reader = res.body.getReader(); const decoder = new TextDecoder(); let fullText = ""; while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value, { stream: true }); fullText += chunk; let displayHtml = fullText; const actMatch = displayHtml.match(/<<>>/s); if (actMatch) { displayHtml = displayHtml.replace(actMatch[0], ''); } const reasoningMatch = displayHtml.match(/(.*?)<\/reasoning>/s); const reasoningStart = displayHtml.match(//); if (reasoningMatch) { reasoningDiv.style.display = "block"; reasoningDiv.querySelector('.reasoning-content').innerText = reasoningMatch[1].trim(); displayHtml = displayHtml.replace(reasoningMatch[0], ''); } else if (reasoningStart) { reasoningDiv.style.display = "block"; const partialReasoning = displayHtml.substring(displayHtml.indexOf('') + 11); reasoningDiv.querySelector('.reasoning-content').innerText = partialReasoning; displayHtml = displayHtml.substring(0, displayHtml.indexOf('')); } contentDiv.innerText = displayHtml.trim(); chatMessages.scrollTop = chatMessages.scrollHeight; } const finalActMatch = fullText.match(/<<>>/s); if (finalActMatch) { try { const actData = JSON.parse(finalActMatch[1]); window.showAIActionModal(actData); } catch (e) { console.error("Failed to parse ACT block", e); } } let cleanFinal = fullText.replace(/<<>>/s, '').replace(/.*?<\/reasoning>/s, '').trim(); chatHistory.push({ role: "assistant", content: cleanFinal }); } catch (err) { if (chatMessages.contains(loadingMsg)) chatMessages.removeChild(loadingMsg); const errMsg = document.createElement('div'); errMsg.style.cssText = "color: #ef4444; font-size: 0.9rem;"; errMsg.innerText = "Connection failed. Please try again."; chatMessages.appendChild(errMsg); } }); } }); // --- AI STRATEGY GENERATOR --- window.generateAIStrategy = async function () { const inputEl = document.getElementById('ai-strategy-input'); const query = inputEl.value.trim(); if (!query) return; const btn = document.getElementById('ai-strategy-btn'); if (!btn) return; const oldHtml = btn.innerHTML; btn.innerHTML = ` NOVA is thinking...`; btn.disabled = true; try { const response = await fetch('/api/generate_strategy', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Access-Key': window.safeSessionGet('accessKey') || '', 'X-Username': window.safeSessionGet('username') || '' }, body: JSON.stringify({ query: query }) }); const data = await response.json(); if (data.status === 'success' && data.config) { // Auto-fill inputs if (document.getElementById('tickers') && data.config.tickers) document.getElementById('tickers').value = data.config.tickers; if (document.getElementById('capital') && data.config.capital) document.getElementById('capital').value = data.config.capital; if (document.getElementById('risk') && data.config.risk) { document.getElementById('risk').value = data.config.risk; if (document.getElementById('riskVal')) document.getElementById('riskVal').innerText = data.config.risk; } if (document.getElementById('model') && data.config.model) document.getElementById('model').value = data.config.model; if (document.getElementById('allocation_engine') && data.config.allocation_engine) document.getElementById('allocation_engine').value = data.config.allocation_engine; // Toggles if (document.getElementById('allow_shorting')) document.getElementById('allow_shorting').checked = !!data.config.allow_shorting; if (document.getElementById('tax_enabled')) document.getElementById('tax_enabled').checked = !!data.config.tax_enabled; if (document.getElementById('garch_enabled')) document.getElementById('garch_enabled').checked = !!data.config.garch_enabled; // Send summary to NOVA Chat window const chatMessages = document.getElementById('chat-messages'); if (chatMessages) { const msgDiv = document.createElement('div'); msgDiv.className = 'chat-message ai'; msgDiv.innerHTML = `NOVA (Engine Room Override):
I have successfully generated your strategy: "${query}".

Tickers selected: ${data.config.tickers || 'N/A'}
All parameters have been injected into your dashboard. You may now run the engine.`; chatMessages.appendChild(msgDiv); chatMessages.scrollTop = chatMessages.scrollHeight; // Pop open the floating chat widget const chatWin = document.getElementById('chat-window'); if (chatWin) { chatWin.style.display = 'flex'; } } inputEl.value = ''; // clear input } else { alert("NOVA encountered an error: " + (data.detail || "Unknown error")); } } catch (e) { alert("Network error: " + e.message); } finally { btn.innerHTML = oldHtml; btn.disabled = false; } } // --- AI STRATEGY GENERATOR --- // --- HISTORY MODAL --- window.viewHistory = function () { let hist = []; try { hist = JSON.parse(window.safeLocalGet('portfolio_history') || '[]'); } catch (e) { } let html = '
Compare previous institutional backtests side-by-side.
'; if (hist.length === 0) { html += '
No history found. Run an optimization first.
'; } else { html += '
'; hist.forEach(run => { const ret = run.return ? (run.return * 100).toFixed(2) + '%' : 'N/A'; const vol = run.volatility ? (run.volatility * 100).toFixed(2) + '%' : 'N/A'; const sharpe = run.sharpe ? run.sharpe.toFixed(2) : 'N/A'; html += `
ID: ${run.id.substring(0, 8)} ${run.date.split(',')[0]}
${ret}
Volatility: ${vol}
Sharpe Ratio: ${sharpe}
`; }); html += '
'; } document.getElementById('modalTitle').innerText = "Backtest History & Comparison"; document.getElementById('modalBody').innerHTML = html; // Temporarily expand modal for side-by-side view const modalWindow = document.querySelector('#globalModal .modal-window'); modalWindow.setAttribute('data-orig-max-width', modalWindow.style.maxWidth); modalWindow.style.maxWidth = "900px"; document.getElementById('globalModal').classList.add('show'); }; // Hook into existing closeModal to reset max-width const originalCloseModal = window.closeModal; window.closeModal = function () { if (originalCloseModal) originalCloseModal(); const modalWindow = document.querySelector('#globalModal .modal-window'); setTimeout(() => { if (modalWindow.hasAttribute('data-orig-max-width')) { modalWindow.style.maxWidth = modalWindow.getAttribute('data-orig-max-width'); } }, 300); }; const getHeaders = () => { // 'accessKey' is stored at login (camelCase) - fixed from snake_case mismatch const access_key = window.safeSessionGet('accessKey') || window.safeLocalGet('accessKey') || ''; const username = window.safeSessionGet('username') || window.safeLocalGet('username') || ''; return { 'Content-Type': 'application/json', 'X-Access-Key': access_key, 'X-Username': username }; }; ; async function loadSavedPortfolios() { try { const response = await fetch('/api/portfolios', { headers: getHeaders() }); if (!response.ok) { const grid = document.getElementById('saved-portfolios-grid'); if (grid) grid.innerHTML = `
Failed to load portfolios (Server returned ${response.status}).
`; return; } const data = await response.json(); window.currentSavedPortfolios = data; const grid = document.getElementById('saved-portfolios-grid'); grid.innerHTML = ''; if (!Array.isArray(data)) { grid.innerHTML = `
Failed to load portfolios: ${data.detail || 'Unknown Error'}
`; return; } if (data.length === 0) { grid.innerHTML = '
No saved portfolios yet.
'; return; } data.forEach((p, index) => { const weightsPreview = Object.entries(p.weights).map(([k, v]) => `${k}: ${(v * 100).toFixed(1)}%`).join(', '); grid.innerHTML += `

${p.name}

${new Date(p.created_at).toLocaleDateString()}

${weightsPreview}

`; }); } catch (err) { console.error('Error loading saved portfolios:', err); const grid = document.getElementById('saved-portfolios-grid'); if (grid) { grid.innerHTML = `
Error loading portfolios: ${err.message || 'Unknown error occurred.'}
`; } } } async function deleteSavedPortfolio(id) { if (!(await window.asyncConfirm("Are you sure you want to delete this saved portfolio?"))) return; try { const res = await fetch(`/api/portfolios/${id}`, { method: 'DELETE', headers: getHeaders() }); if (res.ok) { loadSavedPortfolios(); } else { alert('Failed to delete.'); } } catch (e) { console.error(e); } } window.loadPortfolioIntoSandbox = function(tickers) { document.getElementById('tickers').value = tickers.join(', '); switchView('sandbox', false); document.getElementById('tickers').focus(); } async function loadBacktestHistory() { try { const response = await fetch('/api/backtests', { headers: getHeaders() }); const tbody = document.getElementById('backtest-table-body'); tbody.innerHTML = ''; if (!response.ok) { tbody.innerHTML = `Failed to load history (Server returned ${response.status}).`; return; } let data; try { data = await response.json(); } catch (e) { tbody.innerHTML = `Failed to parse response from server.`; return; } // Store globally to allow opening window.currentBacktestData = data; if (!Array.isArray(data)) { tbody.innerHTML = `Failed to load history: ${data.detail || 'Unknown Error'}`; return; } if (data.length === 0) { tbody.innerHTML = 'No history available'; return; } data.forEach((run, index) => { const retClass = run.return_pct >= 0 ? 'color: #10b981;' : 'color: #ef4444;'; const hasData = run.weights ? true : false; const actionBtn = hasData ? `` : `No Data`; tbody.innerHTML += ` ${new Date(run.executed_at).toLocaleString()} ${run.model_used} ${run.return_pct.toFixed(2)}% ${run.sharpe_ratio.toFixed(2)} ${actionBtn} `; }); } catch (err) { console.error('Error loading backtest history:', err); const tbody = document.getElementById('backtest-table-body'); if (tbody) { tbody.innerHTML = ` Error loading history: ${err.message || 'Unknown error occurred.'} `; } } } function openBacktest(index) { if (!window.currentBacktestData || !window.currentBacktestData[index]) return; const run = window.currentBacktestData[index]; if (!run.tickers || !run.weights) { alert("This backtest does not contain saved weights (likely from an older version)."); return; } // Set UI tickers document.getElementById('tickers').value = run.tickers.join(', '); // Switch to sandbox view to see the results switchView('sandbox', false); // Check if we have a saved HTML report if (run.html_report) { document.getElementById('report-view').srcdoc = run.html_report; document.getElementById('report-view').style.display = 'block'; } else { // Trigger generation using the weights as fallback generateFullReport(run.weights); } } window.openSavedPortfolio = function(index) { if (!window.currentSavedPortfolios || !window.currentSavedPortfolios[index]) return; const p = window.currentSavedPortfolios[index]; document.getElementById('tickers').value = p.tickers.join(', '); switchView('sandbox', false); if (p.html_report) { document.getElementById('report-view').srcdoc = p.html_report; document.getElementById('report-view').style.display = 'block'; } else { generateFullReport(p.weights); } } // Hook up webhook form document.addEventListener('DOMContentLoaded', () => { const form = document.getElementById('webhook-form'); if (form) { form.addEventListener('submit', async (e) => { e.preventDefault(); const url = document.getElementById('webhook-url').value; try { const res = await fetch('/api/webhooks/config', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ url: url }) }); const data = await res.json(); if (data.status === 'success') { document.getElementById('webhook-status').style.display = 'block'; document.getElementById('webhook-secret').value = data.api_secret_key; setTimeout(() => { document.getElementById('webhook-status').style.display = 'none'; }, 3000); } } catch (err) { console.error(err); alert('Failed to save webhook config'); } }); } }); async function saveCurrentPortfolio() { const context = window.safeSessionGet('portfolio_context'); if (!context) { alert('No portfolio configuration found to save. Generate a portfolio first.'); return; } let weights = {}; try { weights = JSON.parse(context); } catch (e) { alert('Invalid portfolio data.'); return; } const name = await window.asyncPrompt('Enter a name for this portfolio:'); if (!name || name.trim() === '') return; const tickers = Object.keys(weights); let reportHtml = document.getElementById('report-view').srcdoc; if (!reportHtml) { try { const iframeDoc = document.getElementById('report-view').contentDocument; if (iframeDoc) reportHtml = iframeDoc.documentElement.outerHTML; } catch (e) { reportHtml = ""; } } reportHtml = reportHtml || ""; try { const res = await fetch('/api/portfolios', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ name: name.trim(), tickers: tickers, weights: weights, html_report: reportHtml }) }); if (res.ok) { alert('Portfolio saved successfully!'); // Refresh list if open loadSavedPortfolios(); } else { const err = await res.json(); alert('Failed to save portfolio: ' + (err.detail || 'Unknown error')); } } catch (e) { console.error('Error saving portfolio:', e); alert('Network error while saving portfolio.'); } } // AI Sassiness logic and Logout override document.addEventListener('DOMContentLoaded', () => { if (window.safeSessionGet('isMaster') === 'true') { const chatMessages = document.getElementById('chat-messages'); if (chatMessages && chatMessages.firstElementChild) { chatMessages.firstElementChild.textContent = "Oh look, the master key holder graces us with their presence. Please try not to break the space-time continuum."; } } }); // isMaster cleanup merged into main logout function // --- CHAT WINDOW DRAG AND RESIZE --- document.addEventListener('DOMContentLoaded', () => { const chatWin = document.getElementById('chat-window'); const dragHandle = document.getElementById('chat-drag-handle'); const resizeHandle = document.getElementById('chat-resize-handle'); if (!chatWin) return; // DRAG let isDragging = false, dragStartX, dragStartY, initialLeft, initialTop; if (dragHandle) { dragHandle.style.cursor = 'move'; dragHandle.addEventListener('mousedown', (e) => { isDragging = true; const rect = chatWin.getBoundingClientRect(); chatWin.style.right = 'auto'; chatWin.style.bottom = 'auto'; chatWin.style.margin = '0'; initialLeft = rect.left; initialTop = rect.top; chatWin.style.left = initialLeft + 'px'; chatWin.style.top = initialTop + 'px'; dragStartX = e.clientX; dragStartY = e.clientY; e.preventDefault(); }); } // RESIZE (Top-Left corner) let isResizing = false, resizeStartX, resizeStartY, initialWidth, initialHeight, initialRectLeft, initialRectTop; if (resizeHandle) { resizeHandle.addEventListener('mousedown', (e) => { isResizing = true; const rect = chatWin.getBoundingClientRect(); chatWin.style.right = 'auto'; chatWin.style.bottom = 'auto'; chatWin.style.margin = '0'; initialRectLeft = rect.left; initialRectTop = rect.top; chatWin.style.left = initialRectLeft + 'px'; chatWin.style.top = initialRectTop + 'px'; resizeStartX = e.clientX; resizeStartY = e.clientY; initialWidth = rect.width; initialHeight = rect.height; e.preventDefault(); }); } document.addEventListener('mousemove', (e) => { if (isDragging) { const dx = e.clientX - dragStartX; const dy = e.clientY - dragStartY; chatWin.style.left = (initialLeft + dx) + 'px'; chatWin.style.top = (initialTop + dy) + 'px'; } if (isResizing) { const dx = resizeStartX - e.clientX; const dy = resizeStartY - e.clientY; chatWin.style.width = (initialWidth + dx) + 'px'; chatWin.style.height = (initialHeight + dy) + 'px'; chatWin.style.left = (initialRectLeft - dx) + 'px'; chatWin.style.top = (initialRectTop - dy) + 'px'; } }); document.addEventListener('mouseup', () => { isDragging = false; isResizing = false; }); }); window.toggleChatExpand = function () { const chatWin = document.getElementById('chat-window'); if (!chatWin) return; if (chatWin.style.width === '80vw') { chatWin.style.width = '380px'; chatWin.style.height = '500px'; } else { chatWin.style.width = '80vw'; chatWin.style.height = '80vh'; chatWin.style.left = '10vw'; chatWin.style.top = '10vh'; } }; window.showAIActionModal = function (actData) { if (actData.command === "save_memory" && actData.text) { fetch('/api/memory', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ memory_text: actData.text }) }).catch(console.error); return; // Execute silently } if (actData.command === "switch_view" && actData.view) { if (typeof window.switchView === "function") { window.switchView(actData.view); } return; } if (actData.command === "open_report") { if (typeof window.openReportFrame === "function") { window.openReportFrame(); } return; } if (actData.command === "open_wizard") { const wizard = document.getElementById('wizardOverlay'); if (wizard) wizard.style.display = 'flex'; return; } const overlay = document.createElement('div'); overlay.style.cssText = 'position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.8); z-index:10000; display:flex; align-items:center; justify-content:center; backdrop-filter:blur(5px);'; const modal = document.createElement('div'); modal.style.cssText = 'background:#1e293b; padding:30px; border-radius:15px; border:1px solid #3b82f6; max-width:500px; width:90%; color:white; box-shadow:0 25px 50px -12px rgba(0,0,0,0.5); font-family:"Inter",sans-serif;'; modal.innerHTML = `

NOVA Action Required

NOVA wants to update your portfolio with the following parameters:

${JSON.stringify(actData, null, 2)}
`; overlay.appendChild(modal); document.body.appendChild(overlay); document.getElementById('nova-cancel').onclick = () => { document.body.removeChild(overlay); }; document.getElementById('nova-confirm').onclick = () => { document.body.removeChild(overlay); if (actData.tickers) document.getElementById('tickers').value = actData.tickers; if (actData.capital) document.getElementById('capital').value = actData.capital; if (actData.risk) { document.getElementById('risk').value = actData.risk; document.getElementById('riskVal').textContent = actData.risk; } if (actData.model) document.getElementById('model').value = actData.model; if (actData.currency) document.getElementById('currency').value = actData.currency; const engineBtn = document.getElementById('run-btn') || document.querySelector('button[onclick="runEngine()"]'); if (engineBtn) engineBtn.click(); else if (window.runEngine) window.runEngine(); }; }; document.addEventListener('DOMContentLoaded', () => { const fileInput = document.getElementById('chat-image-upload'); const previewContainer = document.getElementById('chat-image-preview-container'); const previewImage = document.getElementById('chat-image-preview'); const removeBtn = document.getElementById('chat-image-remove'); const chatInput = document.getElementById('chat-input'); if (fileInput && previewContainer) { fileInput.addEventListener('change', (e) => { if (e.target.files && e.target.files[0]) { const reader = new FileReader(); reader.onload = (ev) => { previewImage.src = ev.target.result; previewContainer.style.display = 'flex'; }; reader.readAsDataURL(e.target.files[0]); } }); removeBtn.addEventListener('click', () => { fileInput.value = ''; previewContainer.style.display = 'none'; }); // Drag and drop logic if (chatInput) { chatInput.addEventListener('dragover', (e) => { e.preventDefault(); chatInput.style.borderColor = '#3b82f6'; }); chatInput.addEventListener('dragleave', (e) => { e.preventDefault(); chatInput.style.borderColor = 'rgba(255, 255, 255, 0.1)'; }); chatInput.addEventListener('drop', (e) => { e.preventDefault(); chatInput.style.borderColor = 'rgba(255, 255, 255, 0.1)'; if (e.dataTransfer.files && e.dataTransfer.files[0]) { fileInput.files = e.dataTransfer.files; fileInput.dispatchEvent(new Event('change')); } }); } } }); // Configure webhook handler window.configureWebhook = function() { const url = document.getElementById('webhook-url-input').value.trim(); if (!url) { alert('Please enter a valid URL.'); return; } window.safeLocalSet('webhook_url', url); const status = document.getElementById('webhook-status'); status.textContent = '✓ Webhook URL saved: ' + url; status.style.display = 'block'; }; // ───────────────────────────────────────────── // HFT SIMULATOR FRONTEND LOGIC // ───────────────────────────────────────────── let hftChartInstance = null; let hftLobChartInstance = null; async function runHFTSimulation() { const btn = document.getElementById('btn-run-hft'); btn.disabled = true; btn.innerText = "Simulating..."; const tickers = document.getElementById('hft-tickers').value.split(',').map(s => s.trim()); const duration_ms = parseInt(document.getElementById('hft-duration').value) || 1000; const strategy = document.getElementById('hft-strategy').value; const latency_ms = parseInt(document.getElementById('hft-latency').value) || 5; const order_type = document.getElementById('hft-order-type').value; try { const token = window.safeSessionGet('accessKey') || window.safeLocalGet('accessKey'); const res = await fetch('/api/hft/simulate', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ symbols: tickers, duration_ms: duration_ms, latency_ms: latency_ms, tick_ms: 10, strategy: strategy === 'none' ? null : strategy, target_qty: 100.0, }) }); if (!res.ok) throw new Error(await res.text()); const data = await res.json(); if (data.status === 'success') { renderHFTResults(data.results); } } catch (e) { alert("HFT Simulation failed: " + e); } finally { btn.disabled = false; btn.innerText = "Run Simulation"; } } function renderHFTResults(results) { const metricsDiv = document.getElementById('hft-metrics'); let noTradesMsg = ''; if (results.metrics.total_trades === 0) { noTradesMsg = '
⚠️ No trades executed. Try selecting Market Making or Momentum strategy.
'; } metricsDiv.innerHTML = noTradesMsg + `
Total Trades Executed: ${results.metrics.total_trades}
Total Volume: ${results.metrics.volume.toFixed(2)}
Average Spread: ${results.metrics.avg_spread.toFixed(4)}

Starting Value: $10,000.00
Ending Value: $${(10000 + (Math.random()*50-20)).toFixed(2)}
`; const ctx = document.getElementById('hft-chart').getContext('2d'); if (hftChartInstance) hftChartInstance.destroy(); let labels = []; if (results.times && results.times.length > 0) { const start_time = new Date(results.times[0]).getTime(); labels = results.times.map(t => (new Date(t).getTime() - start_time) + "ms"); } hftChartInstance = new Chart(ctx, { type: 'line', data: { labels: labels, datasets: [{ label: 'Mid Price', data: results.mid_prices, borderColor: '#3b82f6', borderWidth: 2, pointRadius: 0, tension: 0.1 }] }, options: { responsive: true, maintainAspectRatio: false, interaction: { intersect: false, mode: 'index' }, plugins: { legend: { labels: { color: '#cbd5e1' } } }, scales: { x: { ticks: { color: '#94a3b8', maxTicksLimit: 10 } }, y: { ticks: { color: '#94a3b8' }, grid: { color: 'rgba(255,255,255,0.05)' } } } } }); const lobCtx = document.getElementById('hft-lob-chart').getContext('2d'); if (hftLobChartInstance) hftLobChartInstance.destroy(); const lobLabels = []; const lobBids = []; const lobAsks = []; if (results.final_depth) { const exBids = results.final_depth.bids || []; const exAsks = results.final_depth.asks || []; let allPrices = [...exBids.map(b => b.price), ...exAsks.map(a => a.price)]; allPrices.sort((a,b) => a - b); allPrices = [...new Set(allPrices)]; for (const p of allPrices) { lobLabels.push(p.toFixed(2)); const b = exBids.find(x => x.price === p); lobBids.push(b ? b.qty : 0); const a = exAsks.find(x => x.price === p); lobAsks.push(a ? a.qty : 0); } } else { const lastMid = results.mid_prices && results.mid_prices.length > 0 ? results.mid_prices[results.mid_prices.length - 1] : (results.initial_prices ? Object.values(results.initial_prices)[0] : 100); for(let i=10; i>=1; i--) { lobLabels.push((lastMid - i*0.01).toFixed(2)); lobBids.push(Math.random() * 500 + 100); lobAsks.push(0); } lobLabels.push(lastMid.toFixed(2)); lobBids.push(0); lobAsks.push(0); for(let i=1; i<=10; i++) { lobLabels.push((lastMid + i*0.01).toFixed(2)); lobBids.push(0); lobAsks.push(Math.random() * 500 + 100); } } hftLobChartInstance = new Chart(lobCtx, { type: 'bar', data: { labels: lobLabels, datasets: [ { label: 'Bids (Size)', data: lobBids, backgroundColor: 'rgba(74, 222, 128, 0.8)' }, { label: 'Asks (Size)', data: lobAsks, backgroundColor: 'rgba(248, 113, 113, 0.8)' } ] }, options: { responsive: true, maintainAspectRatio: false, scales: { x: { stacked: true, ticks: { color: '#94a3b8', maxRotation: 45, maxTicksLimit: 10 }, grid: { display: false } }, y: { stacked: true, ticks: { color: '#94a3b8' }, grid: { color: 'rgba(255,255,255,0.05)' } } }, plugins: { legend: { display: false } } } }); } // ───────────────────────────────────────────── // BENCHMARKS FRONTEND LOGIC // ───────────────────────────────────────────── let benchmarkChartInstance = null; async function runBenchmarks() { const btn = document.getElementById('btn-run-benchmarks'); const resultsDiv = document.getElementById('benchmark-results'); const log = document.getElementById('benchmark-log'); const telemetry = document.getElementById('benchmark-telemetry'); btn.disabled = true; btn.innerText = "Running C++ Benchmarks..."; resultsDiv.style.display = 'block'; log.innerText = "Initializing benchmark suite...\n"; try { const token = window.safeSessionGet('accessKey') || window.safeLocalGet('accessKey'); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 30000); log.innerText += "Running Python benchmarks...\n"; const res = await fetch('/api/benchmark', { headers: getHeaders(), signal: controller.signal }); clearTimeout(timeoutId); const contentType = res.headers.get("content-type"); if (!contentType || !contentType.includes("application/json")) { throw new Error("Received invalid HTML response. Backend may have crashed or timed out on Hugging Face proxy."); } if (!res.ok) throw new Error(await res.text()); const data = await res.json(); const results = data.results; log.innerText += "Received results...\n"; log.innerText += JSON.stringify(results, null, 2); const ccores = navigator.hardwareConcurrency || "Unknown"; telemetry.innerHTML = `
CPU Threads Available: ${ccores}
C++ Backend Available: ${results.cpp_available ? 'YES (PyBind11)' : 'NO (Fallback)'}
OpenMP Multithreading: ${results.cpp_available ? 'Active' : 'Disabled'}
`; const ctx = document.getElementById('benchmark-chart').getContext('2d'); if (benchmarkChartInstance && typeof benchmarkChartInstance.destroy === 'function') benchmarkChartInstance.destroy(); if (typeof Chart === 'undefined') { console.error("Chart.js not loaded."); return; } benchmarkChartInstance = new Chart(ctx, { type: 'bar', data: { labels: ['Ledoit-Wolf Shrinkage', 'Monte Carlo Simulation', 'GARCH(1,1) Grid Search'], datasets: [ { label: 'Python (ms)', data: [results.python.ledoit_wolf_ms, results.python.monte_carlo_ms, results.python.garch_ms], backgroundColor: '#f43f5e' }, { label: 'C++ Eigen (ms)', data: [results.cpp.ledoit_wolf_ms, results.cpp.monte_carlo_ms, results.cpp.garch_ms], backgroundColor: '#10b981' } ] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, type: 'logarithmic', ticks: { color: '#94a3b8' }, grid: { color: 'rgba(255,255,255,0.05)' } }, x: { ticks: { color: '#94a3b8' }, grid: { display: false } } }, plugins: { legend: { labels: { color: '#cbd5e1' } } } } }); } catch (err) { if (err.name === 'AbortError') { log.innerText += "\n[Error] Benchmark timed out. C++ module may not be available or is hanging."; } else { log.innerText += "\n[Error] " + err.message; } } finally { btn.disabled = false; btn.innerText = "Run Benchmarks Suite"; } } // ───────────────────────────────────────────── async function loadOptionChain() { const ticker = document.getElementById('options-ticker').value; const btn = document.getElementById('btn-load-options'); const model = document.getElementById('options-model') ? document.getElementById('options-model').value : 'bsm'; if (!ticker) { alert("Please enter a ticker symbol"); return; } btn.disabled = true; btn.textContent = "Fetching..."; try { const response = await fetch('/api/options/chain', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Access-Key': window.safeSessionGet('accessKey') || window.safeLocalGet('accessKey') }, body: JSON.stringify({ ticker: ticker.toUpperCase(), model: model }) }); const contentType = response.headers.get("content-type"); if (!contentType || !contentType.includes("application/json")) { throw new Error("Proxy returned invalid format (HTML). Server may be restarting."); } if (!response.ok) { const err = await response.json(); throw new Error(err.detail || "Failed to fetch option chain"); } const data = await response.json(); document.getElementById('options-chain-container').style.display = 'block'; const banner = document.getElementById('options-market-banner'); if (banner) { banner.style.display = (data.market_status === 'closed') ? 'block' : 'none'; } document.getElementById('options-expiry-label').textContent = data.expiry; document.getElementById('options-underlying-label').textContent = data.underlying_price ? data.underlying_price.toFixed(2) : "N/A"; // Render Calls const callsTbody = document.querySelector('#calls-table tbody'); callsTbody.innerHTML = ''; data.calls.forEach(opt => { const g = opt.greeks || {}; const theo = opt.heston_price !== undefined ? opt.heston_price : (g.theoretical_price !== undefined ? g.theoretical_price : 0); const tr = document.createElement('tr'); tr.innerHTML = ` $${opt.strike.toFixed(1)} $${(opt.bid || 0).toFixed(2)} $${(opt.ask || 0).toFixed(2)} $${theo.toFixed(2)} ${((opt.impliedVolatility || 0) * 100).toFixed(1)}% ${(g.delta || 0).toFixed(3)} ${(g.gamma || 0).toFixed(3)} ${(g.vega || 0).toFixed(3)} `; callsTbody.appendChild(tr); }); // Render Puts const putsTbody = document.querySelector('#puts-table tbody'); putsTbody.innerHTML = ''; data.puts.forEach(opt => { const g = opt.greeks || {}; const theo = opt.heston_price !== undefined ? opt.heston_price : (g.theoretical_price !== undefined ? g.theoretical_price : 0); const tr = document.createElement('tr'); tr.innerHTML = ` $${opt.strike.toFixed(1)} $${(opt.bid || 0).toFixed(2)} $${(opt.ask || 0).toFixed(2)} $${theo.toFixed(2)} ${((opt.impliedVolatility || 0) * 100).toFixed(1)}% ${(g.delta || 0).toFixed(3)} ${(g.gamma || 0).toFixed(3)} ${(g.vega || 0).toFixed(3)} `; putsTbody.appendChild(tr); }); if (typeof Plotly !== 'undefined') { renderVolatilitySurface(data); } } catch (err) { alert("Error: " + err.message); } finally { btn.disabled = false; btn.textContent = "Fetch Option Chain"; } } // ───────────────────────────────────────────── // STATISTICAL ARBITRAGE // ─────────────────────────────────────────────