portfolio_opt / static /app_clean.js
engineportf's picture
Upload folder using huggingface_hub
ba790e8 verified
Raw
History Blame Contribute Delete
12 kB
// Global Variables
let debounceTimer;
// --- INITIALIZATION ---
document.addEventListener('DOMContentLoaded', () => {
// Expandable Cards to Modal Logic
document.addEventListener('click', (e) => {
const card = e.target.closest('.expandable-card');
const isExpandBtn = e.target.closest('.card-expand-btn');
if (card && (isExpandBtn || e.target.tagName === 'H3' || e.target.closest('.card-header-flex'))) {
// Extract content
const title = card.querySelector('h3').innerText;
const bodyHtml = card.querySelector('.card-body').innerHTML;
// Populate modal
document.getElementById('modalTitle').innerText = title;
document.getElementById('modalBody').innerHTML = bodyHtml;
// Show modal
document.getElementById('globalModal').classList.add('show');
}
});
window.closeModal = function() {
document.getElementById('globalModal').classList.remove('show');
};
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');
};
// Mouse Glow Tracking
document.addEventListener("mousemove", (e) => {
document.querySelectorAll(".mouse-glow, .glass-panel, .expandable-card").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]);
}
}
});
}
// 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('#', '');
if (hash) {
switchView(hash, false);
} else {
switchView('hero', false);
}
}
});
// Check initial hash
const initialHash = window.location.hash.replace('#', '');
if (initialHash) {
switchView(initialHash, false);
}
});
// --- NAVIGATION ROUTER ---
window.switchView = function(viewId, pushHistory = true) {
document.querySelectorAll('.view-section').forEach(el => {
el.classList.remove('active');
el.style.opacity = 0;
});
document.querySelectorAll('.sidebar-link').forEach(el => el.classList.remove('active'));
const targetView = document.getElementById('view-' + viewId);
if(targetView) {
targetView.classList.add('active');
// Elegant GSAP fade in
if (window.gsap) {
gsap.fromTo(targetView,
{ opacity: 0, y: 30 },
{ opacity: 1, y: 0, duration: 0.6, ease: "power2.out" }
);
} else {
targetView.style.opacity = 1;
}
}
const link = document.querySelector(`.sidebar-link[data-target="${viewId}"]`);
if(link) link.classList.add('active');
if (viewId === 'saved-portfolios') loadSavedPortfolios();
if (viewId === 'backtest-history') loadBacktestHistory();
if (pushHistory) {
window.history.pushState({ viewId: viewId }, '', '#' + viewId);
}
};
// --- GSAP ANIMATIONS ---
function initGSAPAnimations() {
if (typeof gsap === 'undefined') return;
gsap.registerPlugin(ScrollTrigger);
// Staggered entry for Model Zoo Cards
document.querySelectorAll('.zoo-grid').forEach(grid => {
const cards = grid.querySelectorAll('.expandable-card');
if (cards.length === 0) return;
gsap.fromTo(cards,
{ opacity: 0, y: 50 },
{
opacity: 1,
y: 0,
duration: 0.8,
stagger: 0.15,
ease: "power3.out",
scrollTrigger: {
trigger: grid,
start: "top 85%"
}
}
);
});
}
// --- 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 += `<div class="ticker-item">
<strong style="color: #f8fafc; margin-right: 8px;">${item.name}</strong>
<span>$${item.price}</span>
<span style="${colorClass} margin-left: 6px; font-weight: 500;">${sign}${(item.change*100).toFixed(2)}%</span>
</div>`;
});