// Safe storage wrappers moved to index.html and options.html tags // Global Variables let debounceTimer; // --- 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); } else { // Handle hash fallback or default home const hash = window.location.hash.replace('#', '').replace(/^view-/, ''); if (hash) { switchView(hash, false); } else if (!window.location.pathname.includes('/options')) { switchView('hero', false); } } }); // Check initial hash const initialHash = window.location.hash.replace('#', '').replace(/^view-/, ''); 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) { 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 = ''; // Reset to let class dictate // 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 = ''; 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); } }; // --- 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 --- 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() * 8; } else if (simProgress < 85) { simProgress += Math.random() * 3; } else if (simProgress < 98) { simProgress += Math.random() * 0.5; } 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); 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))); } 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'); // Check if report actually exists before opening iframe try { const checkRes = await fetch('/report'); if (!checkRes.ok) { alert("Report generation failed or returned a blank response. Check server logs."); return; } } catch (e) { alert("Error fetching report."); 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(); }; 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 = []; 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.style.cssText = "background: rgba(255,255,255,0.1); padding: 10px 14px; border-radius: 12px; border-top-right-radius: 4px; align-self: flex-end; max-width: 85%; color: white;"; 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]" : "") }); 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 }) }); chatMessages.removeChild(loadingMsg); const aiMsgContainer = document.createElement('div'); aiMsgContainer.style.cssText = "background: rgba(59, 130, 246, 0.1); padding: 12px 16px; border-radius: 12px; border-top-left-radius: 4px; align-self: flex-start; max-width: 85%; color: #e2e8f0; line-height: 1.5; display: flex; flex-direction: column; gap: 8px;"; 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; } } // --- REPORT FRAME LOGIC --- window.openReportFrame = async function () { const reportContainer = document.getElementById('reportContainer'); const reportView = document.getElementById('report-view'); // Check if report actually exists before opening iframe try { const checkRes = await fetch('/report'); if (!checkRes.ok) { alert("Report generation failed or returned a blank response. Check server logs."); return; } } catch (e) { alert("Error fetching report."); 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(); }; 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 = function () { window.safeSessionRem('accessKey'); window.location.href = '/'; }; // --- 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); 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) benchmarkChartInstance.destroy(); 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 }) }); 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 // ───────────────────────────────────────────── let statArbChartInstance = null; async function runStatArbScan() { const tickersStr = document.getElementById('statarb-tickers').value; const btn = document.getElementById('btn-run-statarb'); if (!tickersStr) return; const tickers = tickersStr.split(',').map(t => t.trim().toUpperCase()).filter(t => t); if (tickers.length < 2) { alert("Please enter at least two tickers"); return; } btn.disabled = true; btn.textContent = "Scanning..."; try { const response = await fetch('/api/statarb/scan', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Access-Key': window.safeSessionGet('accessKey') || window.safeLocalGet('accessKey') }, body: JSON.stringify({ tickers: tickers, run_backtest: true }) }); if (!response.ok) { const err = await response.json(); throw new Error(err.detail || "Failed to scan pairs"); } const data = await response.json(); document.getElementById('statarb-results-container').style.display = 'block'; // Render Pairs Table const pairsTbody = document.querySelector('#pairs-table tbody'); pairsTbody.innerHTML = ''; data.pairs.forEach(p => { const tr = document.createElement('tr'); tr.innerHTML = ` ${p.pair[0]} / ${p.pair[1]} ${p.p_value.toFixed(4)} ${p.half_life.toFixed(1)} days ${p.hedge_ratio.toFixed(3)} `; pairsTbody.appendChild(tr); }); // Render Top Pair Backtest const bt = data.top_pair_backtest; if (bt && !bt.error) { document.getElementById('statarb-top-pair').textContent = bt.pair; document.getElementById('statarb-ret').textContent = (bt.total_return * 100).toFixed(2) + '%'; document.getElementById('statarb-vol').textContent = (bt.annualized_volatility * 100).toFixed(2) + '%'; document.getElementById('statarb-sharpe').textContent = bt.sharpe_ratio.toFixed(2); if (statArbChartInstance) { statArbChartInstance.destroy(); } const ctx = document.getElementById('statarb-chart').getContext('2d'); statArbChartInstance = new Chart(ctx, { type: 'line', data: { labels: bt.dates, datasets: [ { label: 'Z-Score', data: bt.z_scores, borderColor: '#8b5cf6', borderWidth: 1.5, pointRadius: 0, yAxisID: 'y' }, { label: 'Equity Curve', data: bt.equity_curve.map(x => (x - 1) * 100), borderColor: '#4ade80', borderWidth: 2, pointRadius: 0, yAxisID: 'y1' } ] }, options: { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false }, scales: { y: { type: 'linear', display: true, position: 'left', title: {display: true, text: 'Z-Score'} }, y1: { type: 'linear', display: true, position: 'right', grid: {drawOnChartArea: false}, title: {display: true, text: 'PnL %'} }, x: { display: false } }, plugins: { legend: { labels: { color: '#cbd5e1' } } } } }); } } catch (err) { alert("Error: " + err.message); } finally { btn.disabled = false; btn.textContent = "Scan Pairs"; } } // ───────────────────────────────────────────── // CRYPTO ARBITRAGE // ───────────────────────────────────────────── let cryptoArbDepthChartInstance = null; let cryptoArbSpreadChartInstance = null; let spreadHistory = []; async function runCryptoArbScan() { const symbol = document.getElementById('cryptoarb-symbol').value; const capital = document.getElementById('cryptoarb-capital').value; const btn = document.getElementById('btn-run-cryptoarb'); if (!symbol) return; btn.disabled = true; btn.textContent = "Scanning..."; try { const response = await fetch('/api/cryptoarb/scan', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Access-Key': window.safeSessionGet('accessKey') || window.safeLocalGet('accessKey') }, body: JSON.stringify({ symbol: symbol.toUpperCase(), capital: parseFloat(capital) || 10000.0 }) }); if (!response.ok) { const err = await response.json(); throw new Error(err.detail || "Failed to scan order books"); } const data = await response.json(); document.getElementById('cryptoarb-results-container').style.display = 'block'; // Render Prices const pricesDiv = document.getElementById('cryptoarb-prices'); pricesDiv.innerHTML = ''; for (const [exchange, p] of Object.entries(data.prices)) { const box = document.createElement('div'); box.style = 'background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.05); padding: 1rem; border-radius: 4px;'; box.innerHTML = `
${exchange}
Bid: $${(p.bid || 0).toFixed(2)} Ask: $${(p.ask || 0).toFixed(2)}
`; pricesDiv.appendChild(box); } // Render Opportunities const oppsTbody = document.querySelector('#cryptoarb-opps-table tbody'); if (oppsTbody) { oppsTbody.innerHTML = ''; if (data.opportunities && data.opportunities.length > 0) { data.opportunities.forEach(opp => { const tr = document.createElement('tr'); tr.innerHTML = ` Buy ${opp.buy_exchange} / Sell ${opp.sell_exchange} $${opp.buy_price.toFixed(2)} $${opp.sell_price.toFixed(2)} ${(opp.gross_spread_pct * 100).toFixed(3)}% ${(opp.net_spread_pct * 100).toFixed(3)}% `; oppsTbody.appendChild(tr); }); } } // Setup Charts const depthCtx = document.getElementById('cryptoarb-depth-chart').getContext('2d'); const spreadCtx = document.getElementById('cryptoarb-spread-chart').getContext('2d'); if (cryptoArbDepthChartInstance) cryptoArbDepthChartInstance.destroy(); if (cryptoArbSpreadChartInstance) cryptoArbSpreadChartInstance.destroy(); // 1. Order Book Depth Chart if (data.opportunities && data.opportunities.length > 0) { const bestOpp = data.opportunities[0]; const buyEx = bestOpp.buy_exchange; const sellEx = bestOpp.sell_exchange; const exBids = data.prices[sellEx]?.bids || []; const exAsks = data.prices[buyEx]?.asks || []; let allPrices = [...exBids.map(b => b.price), ...exAsks.map(a => a.price)]; allPrices.sort((a,b) => a - b); allPrices = [...new Set(allPrices)]; const labels = allPrices.map(p => p.toFixed(2)); const bids = allPrices.map(p => { const b = exBids.find(x => x.price === p); return b ? b.qty : 0; }); const asks = allPrices.map(p => { const a = exAsks.find(x => x.price === p); return a ? a.qty : 0; }); cryptoArbDepthChartInstance = new Chart(depthCtx, { type: 'bar', data: { labels: labels, datasets: [ { label: `Bids (${sellEx})`, data: bids, backgroundColor: 'rgba(74, 222, 128, 0.7)' }, { label: `Asks (${buyEx})`, data: asks, backgroundColor: 'rgba(248, 113, 113, 0.7)' } ] }, options: { responsive: true, maintainAspectRatio: false, scales: { x: { stacked: true, ticks: { color: '#94a3b8' }, grid: { display: false } }, y: { stacked: true, ticks: { color: '#94a3b8' }, grid: { color: 'rgba(255,255,255,0.05)' } } }, plugins: { legend: { display: false } } } }); // 2. Spread Tracking Chart spreadHistory.push({ time: new Date().toLocaleTimeString(), spread: bestOpp.net_spread_pct * 100 }); if(spreadHistory.length > 20) spreadHistory.shift(); cryptoArbSpreadChartInstance = new Chart(spreadCtx, { type: 'line', data: { labels: spreadHistory.map(h => h.time), datasets: [{ label: 'Net Spread (%)', data: spreadHistory.map(h => h.spread), borderColor: '#60a5fa', backgroundColor: 'rgba(96, 165, 250, 0.1)', fill: true, tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: false, scales: { x: { ticks: { color: '#94a3b8', maxRotation: 0 }, grid: { display: false } }, y: { ticks: { color: '#94a3b8' }, grid: { color: 'rgba(255,255,255,0.05)' } } }, plugins: { legend: { display: false } } } }); } // Render Execution const execDiv = document.getElementById('cryptoarb-execution'); const costDiv = document.getElementById('cryptoarb-cost-breakdown'); if (data.execution && !data.execution.error && data.opportunities && data.opportunities.length > 0) { const e = data.execution; execDiv.style.borderColor = e.status === 'FILLED' ? 'rgba(74, 222, 128, 0.3)' : 'rgba(248, 113, 113, 0.3)'; execDiv.innerHTML = `[${new Date().toISOString()}] EXECUTION SIMULATION\n\n` + `Status: ${e.status}\n` + `Capital Deployed: $${e.capital_deployed.toFixed(2)}\n` + `Target Spread: ${(data.opportunities[0].net_spread_pct * 100).toFixed(3)}%\n` + `Realized Net %: ${(e.net_profit_pct * 100).toFixed(3)}%\n` + `Realized Net USD: $${e.net_profit_usd.toFixed(2)}\n`; costDiv.innerHTML = `
Exchange Fees: -0.200% (Two legs)
Latency Decay (50ms): -${(e.latency_penalty_pct * 100).toFixed(3)}%
Market Impact (Slippage): -${(e.impact_penalty_pct * 100).toFixed(3)}%

Total Lost to Inefficiencies: -${((0.002 + e.latency_penalty_pct + e.impact_penalty_pct)*100).toFixed(3)}%
`; } else { execDiv.style.borderColor = 'rgba(255,255,255,0.05)'; execDiv.innerHTML = "No profitable opportunities found after fees."; costDiv.innerHTML = "No execution attempted."; } } catch (err) { alert("Error: " + err.message); } finally { btn.disabled = false; btn.textContent = "Scan Order Books"; } } document.addEventListener('DOMContentLoaded', () => { // pending_options are kept separate from the main Engine Room's 'tickers' input // so they do not pollute the equities portfolio. }); function renderVolatilitySurface(data) { const strikes = data.calls.map(c => c.strike); const ivs = data.calls.map(c => c.impliedVolatility || 0.2); const exp_days = [7, 14, 30, 60, 90]; const z_data = []; for(let i=0; iRecommended'); out.innerHTML = replyHtml; // Apply configuration if returned if (data.configuration) { const conf = data.configuration; if (conf.opt_capital) document.getElementById('opt-capital').value = conf.opt_capital; if (conf.opt_goal) document.getElementById('opt-goal').value = conf.opt_goal; if (conf.opt_risk) { const el = document.getElementById('opt-risk'); if (el) { el.value = conf.opt_risk; document.getElementById('opt-risk-val').innerText = conf.opt_risk; } } if (conf.tickers) { // Automatically stage the generated options to pending_options so they will be available in Options Explorer let saved = window.safeLocalGet('pending_options') || ''; let vals = saved ? saved.split(',') : []; let newTickers = conf.tickers.split(',').map(x => x.trim()).filter(x => x); newTickers.forEach(t => { if (!vals.includes(t)) vals.push(t); }); window.safeLocalSet('pending_options', vals.join(',')); alert(`NOVA has configured your Options Matrix and staged ${newTickers.length} option(s) in your Options Sandbox!`); } } } catch (err) { out.innerHTML = 'Error: ' + err.message + ''; } } // Custom Alert to prevent iframe blocking window.alert = function(msg) { const toast = document.createElement('div'); toast.style.position = 'fixed'; toast.style.bottom = '20px'; toast.style.right = '20px'; toast.style.background = 'rgba(15, 23, 42, 0.9)'; toast.style.color = '#f8fafc'; toast.style.padding = '15px 25px'; toast.style.borderRadius = '8px'; toast.style.boxShadow = '0 10px 25px rgba(0,0,0,0.5)'; toast.style.borderLeft = '4px solid #3b82f6'; toast.style.zIndex = '10000'; toast.style.fontFamily = 'monospace'; toast.style.transition = 'opacity 0.4s ease'; toast.innerText = msg; document.body.appendChild(toast); setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => toast.remove(), 400); }, 4000); }; // Custom Prompt window.asyncPrompt = function(msg, defaultVal) { return new Promise(resolve => { const bg = document.createElement('div'); bg.style.position = 'fixed'; bg.style.top='0'; bg.style.left='0'; bg.style.width='100vw'; bg.style.height='100vh'; bg.style.background='rgba(0,0,0,0.8)'; bg.style.zIndex='10001'; bg.style.display='flex'; bg.style.alignItems='center'; bg.style.justifyContent='center'; const box = document.createElement('div'); box.style.background='#1e293b'; box.style.padding='20px'; box.style.borderRadius='8px'; box.style.color='#fff'; box.style.display='flex'; box.style.flexDirection='column'; box.style.gap='10px'; const text = document.createElement('div'); text.innerText = msg; const inp = document.createElement('input'); inp.value = defaultVal||''; inp.style.padding='8px'; inp.style.background='#0f172a'; inp.style.color='#fff'; inp.style.border='1px solid #3b82f6'; inp.style.outline='none'; const btn = document.createElement('button'); btn.innerText='Submit'; btn.style.padding='8px'; btn.style.background='#3b82f6'; btn.style.color='#fff'; btn.style.border='none'; btn.style.cursor='pointer'; btn.onclick = () => { bg.remove(); resolve(inp.value); }; box.appendChild(text); box.appendChild(inp); box.appendChild(btn); bg.appendChild(box); document.body.appendChild(bg); inp.focus(); }); }; // Custom Confirm window.asyncConfirm = function(msg) { return new Promise(resolve => { const bg = document.createElement('div'); bg.style.position = 'fixed'; bg.style.top='0'; bg.style.left='0'; bg.style.width='100vw'; bg.style.height='100vh'; bg.style.background='rgba(0,0,0,0.8)'; bg.style.zIndex='10001'; bg.style.display='flex'; bg.style.alignItems='center'; bg.style.justifyContent='center'; const box = document.createElement('div'); box.style.background='#1e293b'; box.style.padding='20px'; box.style.borderRadius='8px'; box.style.color='#fff'; box.style.display='flex'; box.style.flexDirection='column'; box.style.gap='15px'; box.style.minWidth='300px'; const text = document.createElement('div'); text.innerText = msg; const row = document.createElement('div'); row.style.display='flex'; row.style.gap='10px'; row.style.justifyContent='flex-end'; const btnYes = document.createElement('button'); btnYes.innerText='Yes'; btnYes.style.padding='8px 16px'; btnYes.style.background='#ef4444'; btnYes.style.color='#fff'; btnYes.style.border='none'; btnYes.style.cursor='pointer'; btnYes.style.borderRadius='4px'; const btnNo = document.createElement('button'); btnNo.innerText='Cancel'; btnNo.style.padding='8px 16px'; btnNo.style.background='#3b82f6'; btnNo.style.color='#fff'; btnNo.style.border='none'; btnNo.style.cursor='pointer'; btnNo.style.borderRadius='4px'; btnYes.onclick = () => { bg.remove(); resolve(true); }; btnNo.onclick = () => { bg.remove(); resolve(false); }; row.appendChild(btnNo); row.appendChild(btnYes); box.appendChild(text); box.appendChild(row); bg.appendChild(box); document.body.appendChild(bg); }); }; // --- EASTER EGG: THE QUANT'S TERMINAL V2 --- (function() { const konamiCode = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65]; let konamiIndex = 0; const allTrades = [ "[1929-10-29 15:59:59] SELL DJIA 900x @ 230.07 ██████ BLACK TUESDAY", "[1987-10-19 09:31:02] SELL SPX 500x @ 282.70 ██████ PORTFOLIO INSURANCE TRIGGERED", "[1992-09-16 08:00:00] SHORT GBP 10B @ 1.95 ██████ SOROS BREAKS BOE", "[1998-08-17 14:22:11] BUY RUBL 10M @ 0.0041 ██████ LTCM CONVERGENCE TRADE", "[2000-03-10 16:00:00] SELL NDX 200x @ 5048.62 ██████ DOT COM PEAK", "[2008-09-15 06:00:00] SHORT LEH 999x @ 3.65 ██████ LEHMAN BROTHERS — FINAL BELL", "[2010-05-06 14:42:44] BUY ES 1x @ 1056.00 ██████ FLASH CRASH BOTTOM TICK", "[2015-01-15 09:30:00] BUY CHF 50M @ 1.20 ██████ SNB UNPEGS FRANCS", "[2020-04-20 14:08:00] BUY WTI 100x @ -37.63 ██████ OIL GOES NEGATIVE", "[2021-01-28 09:30:01] BUY GME 420x @ 347.51 ██████ DIAMOND HANDS PROTOCOL", "[2022-11-08 10:00:00] SHORT FTT 500x @ 15.00 ██████ FTX LIQUIDITY CRISIS", "[2026-07-01 NOW ] RUN WEALTH_ENGINE ██████ YOU FOUND THE TERMINAL." ]; const asciiArt = `╔══════════════════════════════════════════╗ ║ "The market can stay irrational longer ║ ║ than you can stay solvent." — Keynes ║ ║ ║ ║ Welcome to the inner circle. ║ ╚══════════════════════════════════════════╝`; const quotes = [ "Risk comes from not knowing what you're doing. - Warren Buffett", "In investing, what is comfortable is rarely profitable. - Robert Arnott", "Amateurs think about how much money they can make. Professionals think about how much money they could lose. - Jack Schwager", "Bulls make money, bears make money, pigs get slaughtered. - Unknown", "The elements of good trading are: 1. Cutting losses, 2. Cutting losses, and 3. Cutting losses. - Ed Seykota" ]; let audioCtx = null; function initAudio() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } if (audioCtx.state === 'suspended') { audioCtx.resume(); } } function playTone(freq, type, duration, vol) { if (!audioCtx) return; const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.type = type; osc.frequency.setValueAtTime(freq, audioCtx.currentTime); gain.gain.setValueAtTime(vol, audioCtx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + duration); osc.connect(gain); gain.connect(audioCtx.destination); osc.start(); osc.stop(audioCtx.currentTime + duration); } function playKeyClick() { playTone(400 + Math.random() * 200, 'square', 0.03, 0.05); } document.addEventListener('keydown', function(e) { if (e.keyCode === konamiCode[konamiIndex]) { konamiIndex++; if (konamiIndex === konamiCode.length) { konamiIndex = 0; activateTerminal(); } } else { konamiIndex = 0; } }); function shuffle(array) { let currentIndex = array.length, randomIndex; while (currentIndex !== 0) { randomIndex = Math.floor(Math.random() * currentIndex); currentIndex--; [array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]]; } return array; } function activateTerminal() { if (window.WealthEngineEggs) window.WealthEngineEggs.discover('terminal'); if (document.querySelector('.crt-overlay') && !document.querySelector('.crt-flash')) { return; } initAudio(); if (audioCtx) { const hum = audioCtx.createOscillator(); const humGain = audioCtx.createGain(); hum.type = 'sine'; hum.frequency.setValueAtTime(50, audioCtx.currentTime); humGain.gain.setValueAtTime(0.05, audioCtx.currentTime); hum.connect(humGain); humGain.connect(audioCtx.destination); hum.start(); window.terminalHum = { osc: hum, gain: humGain }; } const overlay = document.createElement('div'); overlay.className = 'crt-overlay'; const canvas = document.createElement('canvas'); canvas.className = 'crt-matrix-canvas'; overlay.appendChild(canvas); const scanlines = document.createElement('div'); scanlines.className = 'crt-scanlines'; const vignette = document.createElement('div'); vignette.className = 'crt-vignette'; const curve = document.createElement('div'); curve.className = 'crt-curve'; const contentContainer = document.createElement('div'); contentContainer.className = 'crt-content-container'; const content = document.createElement('div'); content.id = 'crt-content'; contentContainer.appendChild(content); curve.appendChild(contentContainer); overlay.appendChild(scanlines); overlay.appendChild(vignette); overlay.appendChild(curve); document.body.appendChild(overlay); canvas.width = window.innerWidth; canvas.height = window.innerHeight; const ctx = canvas.getContext('2d'); const matrixChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789$¥€£%αβσΔΓΘ".split(""); const fontSize = 16; const columns = canvas.width / fontSize; const drops = []; for (let x = 0; x < columns; x++) drops[x] = 1; let matrixInterval = setInterval(() => { ctx.fillStyle = "rgba(0, 0, 0, 0.05)"; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = "#0F0"; ctx.font = fontSize + "px monospace"; for (let i = 0; i < drops.length; i++) { const text = matrixChars[Math.floor(Math.random() * matrixChars.length)]; ctx.fillText(text, i * fontSize, drops[i] * fontSize); if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) drops[i] = 0; drops[i]++; } }, 33); overlay.style.display = 'flex'; overlay.classList.add('crt-flash'); let tradesCopy = shuffle([...allTrades]).slice(0, 8); tradesCopy.unshift("WEALTH ENGINE TERMINAL v2.0 — CLASSIFIED\n"); let currentLine = 0; let currentChar = 0; function appendLine(text, cssClass = '') { const lineDiv = document.createElement('div'); lineDiv.className = 'crt-text-line ' + cssClass; lineDiv.innerHTML = text; content.appendChild(lineDiv); contentContainer.scrollTop = contentContainer.scrollHeight; return lineDiv; } let glitchTimer; function triggerGlitch() { curve.classList.add('crt-glitch'); setTimeout(() => { curve.classList.remove('crt-glitch'); }, 200 + Math.random() * 300); } function typeChar() { if (currentLine >= tradesCopy.length) { setTimeout(() => { initInteractivePrompt(); }, 400); return; } const text = tradesCopy[currentLine]; if (currentChar === 0) { const lineDiv = document.createElement('div'); lineDiv.className = 'crt-text-line'; lineDiv.id = 'crt-line-' + currentLine; content.appendChild(lineDiv); if (Math.random() < 0.15) triggerGlitch(); } const lineEl = document.getElementById('crt-line-' + currentLine); if (currentChar < text.length) { lineEl.textContent += text[currentChar]; currentChar++; playKeyClick(); contentContainer.scrollTop = contentContainer.scrollHeight; setTimeout(typeChar, 10 + Math.random() * 30); } else { currentLine++; currentChar = 0; setTimeout(typeChar, 200); } } setTimeout(() => { overlay.classList.remove('crt-flash'); typeChar(); }, 800); function initInteractivePrompt() { const promptLine = document.createElement('div'); promptLine.className = 'crt-prompt-line'; const promptLabel = document.createElement('span'); promptLabel.className = 'crt-prompt'; promptLabel.textContent = 'WEALTH_ENGINE>'; const inputEl = document.createElement('input'); inputEl.type = 'text'; inputEl.className = 'crt-input'; inputEl.spellcheck = false; promptLine.appendChild(promptLabel); promptLine.appendChild(inputEl); content.appendChild(promptLine); inputEl.focus(); contentContainer.scrollTop = contentContainer.scrollHeight; inputEl.addEventListener('keydown', function(e) { initAudio(); playKeyClick(); if (e.key === 'Enter') { const cmd = this.value.trim().toLowerCase(); const rawCmd = this.value; this.disabled = true; this.parentElement.innerHTML = 'WEALTH_ENGINE>' + rawCmd + ''; processCommand(cmd); } }); document.addEventListener('click', () => { if(document.contains(inputEl)) inputEl.focus(); }); } let pnlInterval; function processCommand(cmd) { if (cmd === '') { initInteractivePrompt(); return; } if (cmd.startsWith('decrypt ')) { const key = cmd.split(' ')[1].toUpperCase(); if (key === (window.MASTER_KEY || "7F4B29A1E8C6")) { appendLine("[SYSTEM] ACCESS GRANTED. VAULT UNLOCKED.", "highlight"); if (typeof triggerGlitch === 'function') triggerGlitch(); const overlay = document.querySelector('.crt-overlay'); if (overlay) overlay.style.backgroundColor = "rgba(0, 255, 0, 0.1)"; setTimeout(() => { if (overlay) overlay.style.backgroundColor = ""; if (window.WealthEngineEggs) window.WealthEngineEggs.unlockVault(); executeExit(); setTimeout(() => { if (window.switchView) window.switchView('view-innercircle'); if (typeof renderInnerCircle === 'function') renderInnerCircle(); }, 1000); }, 2000); } else { appendLine("[SYSTEM] ACCESS DENIED. INVALID HASH SEQUENCE.", "highlight"); } setTimeout(initInteractivePrompt, 500); return; } switch(cmd) { case 'help': appendLine("Available commands:"); appendLine(" help - Show this message"); appendLine(" whoami - Display current user context"); appendLine(" trades - Show legendary trade log"); appendLine(" fortune - Wisdom from the masters"); appendLine(" status - Live PnL monitor"); appendLine(" hack - Establish uplink to The Fed"); appendLine(" clear - Clear terminal screen"); appendLine(" exit - Disconnect from terminal"); appendLine(" decrypt - Unlock restricted sectors"); initInteractivePrompt(); break; case 'whoami': appendLine("Rogue Quant. Access Level: CLASSIFIED.", "highlight"); initInteractivePrompt(); break; case 'trades': tradesCopy.forEach(t => appendLine(t)); initInteractivePrompt(); break; case 'fortune': const q = quotes[Math.floor(Math.random() * quotes.length)]; appendLine('"' + q + '"', "highlight"); initInteractivePrompt(); break; case 'status': appendLine("Connecting to portfolio stream..."); const pnlLine = appendLine("PnL: $0.00"); let pnl = 0; if(pnlInterval) clearInterval(pnlInterval); pnlInterval = setInterval(() => { pnl += (Math.random() * 1000) - 400; if(document.contains(pnlLine)) { pnlLine.innerHTML = "PnL: $" + pnl.toFixed(2) + ""; } else { clearInterval(pnlInterval); } }, 500); setTimeout(() => { appendLine("Stream detached."); clearInterval(pnlInterval); initInteractivePrompt(); }, 5000); break; case 'hack': appendLine("Initiating bypass sequence..."); const progressLine = document.createElement('div'); progressLine.className = 'crt-text-line'; progressLine.innerHTML = `Exploiting JPow API:
`; content.appendChild(progressLine); const fill = progressLine.querySelector('.crt-progress-fill'); let w = 0; const hackInt = setInterval(() => { w += Math.random() * 15; if (w > 100) w = 100; fill.style.width = w + '%'; playKeyClick(); if (w >= 100) { clearInterval(hackInt); setTimeout(() => { appendLine("ACCESS GRANTED. MONEY PRINTER GO BRRRRR.", "highlight"); triggerGlitch(); initInteractivePrompt(); }, 500); } }, 200); break; case 'clear': content.innerHTML = ''; initInteractivePrompt(); break; case 'exit': executeExit(); break; default: appendLine(`Command not found: ${cmd}. Type 'help' for available commands.`); initInteractivePrompt(); } } const escHandler = (e) => { if (e.key === 'Escape') { document.removeEventListener('keydown', escHandler); executeExit(); } }; document.addEventListener('keydown', escHandler); function executeExit() { if (pnlInterval) clearInterval(pnlInterval); clearInterval(matrixInterval); content.innerHTML = ''; canvas.style.opacity = '0'; appendLine("


"); const artLine = appendLine(asciiArt, "highlight"); artLine.style.textAlign = "center"; artLine.style.width = "100%"; setTimeout(() => { powerOff(); }, 5000); } function powerOff() { if (window.terminalHum) { window.terminalHum.gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.5); setTimeout(() => window.terminalHum.osc.stop(), 500); } playTone(800, 'sawtooth', 0.6, 0.2); overlay.classList.add('crt-poweroff'); setTimeout(() => { overlay.remove(); window.safeSessionSet('terminal_unlocked', 'true'); const logoTexts = document.querySelectorAll('.logo-text'); logoTexts.forEach(el => { if (!el.parentNode.querySelector('.crt-badge')) { const badge = document.createElement('span'); badge.className = 'crt-badge'; badge.textContent = '🎮'; el.parentNode.appendChild(badge); } }); }, 600); } } })(); /* ========================================================================== EASTER EGG SYSTEM (PHASES 1-4) ========================================================================== */ const MASTER_KEY = "7F4B29A1E8C6"; const FRAGMENTS = { 'terminal': { char: '7', pos: 1 }, 'secret_message': { char: 'F', pos: 2 }, 'shiller_pe': { char: '4', pos: 3 }, 'black_swan': { char: 'B', pos: 4 }, 'random_walk': { char: '2', pos: 5 }, 'fibonacci': { char: '9', pos: 6 }, 'dow_theory': { char: 'A', pos: 7 }, 'quote_day': { char: '1', pos: 8 }, 'phantom_chart': { char: 'E', pos: 9 }, 'oracle': { char: '8', pos: 10 }, 'architect': { char: 'C', pos: 11 }, 'the_map': { char: '6', pos: 12 } };