Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>FinWise — Risk Analyzer</title> | |
| <link rel="stylesheet" href="shared.css"> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script> | |
| <style> | |
| .quiz-card { margin-bottom: 16px; transition: opacity 0.3s; } | |
| .quiz-card.inactive { opacity: 0.4; pointer-events: none; } | |
| .question-num { | |
| font-size: 11px; font-weight: 700; text-transform: uppercase; | |
| letter-spacing: 0.1em; color: var(--cyan); margin-bottom: 6px; | |
| } | |
| .question-text { font-size: 16px; font-weight: 600; margin-bottom: 14px; line-height: 1.4; } | |
| .answer-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } | |
| .answer-btn { | |
| padding: 12px 14px; | |
| background: var(--bg3); | |
| border: 1px solid var(--border); | |
| border-radius: var(--r-sm); | |
| color: var(--text2); | |
| font-family: var(--font-body); | |
| font-size: 13px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| text-align: left; | |
| transition: all var(--transition); | |
| line-height: 1.3; | |
| } | |
| .answer-btn:hover { border-color: var(--border2); color: var(--text); background: var(--card2); } | |
| .answer-btn.selected { border-color: var(--cyan); background: rgba(34,211,238,0.10); color: var(--cyan); } | |
| /* Risk Gauge */ | |
| .gauge-container { position: relative; width: 220px; height: 120px; margin: 0 auto; } | |
| .gauge-svg { width: 220px; height: 120px; } | |
| .gauge-needle { | |
| transform-origin: 110px 110px; | |
| transition: transform 1.2s cubic-bezier(.34,1.56,.64,1); | |
| } | |
| .gauge-center-text { | |
| position: absolute; | |
| bottom: 0; left: 50%; | |
| transform: translateX(-50%); | |
| text-align: center; | |
| } | |
| .gauge-score { font-family: var(--font-head); font-size: 32px; font-weight: 800; } | |
| .gauge-label { font-size: 12px; color: var(--text2); } | |
| /* Heatmap */ | |
| .heatmap-grid { | |
| display: grid; | |
| gap: 3px; | |
| } | |
| .heatmap-cell { | |
| height: 40px; | |
| border-radius: 4px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 11px; | |
| font-weight: 700; | |
| color: white; | |
| transition: transform var(--transition); | |
| cursor: default; | |
| } | |
| .heatmap-cell:hover { transform: scale(1.05); } | |
| .rebal-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 14px; | |
| padding: 14px; | |
| background: var(--bg3); | |
| border-radius: var(--r-sm); | |
| border: 1px solid var(--border); | |
| margin-bottom: 10px; | |
| transition: all var(--transition); | |
| } | |
| .rebal-item:hover { border-color: var(--border2); } | |
| .rebal-action { | |
| font-size: 11px; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| letter-spacing: 0.08em; | |
| margin-bottom: 3px; | |
| } | |
| .rebal-desc { font-size: 13px; color: var(--text2); } | |
| .rebal-arrow { font-size: 20px; margin-left: auto; } | |
| .risk-breakdown { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } | |
| .rb-item { | |
| background: var(--bg3); | |
| border-radius: var(--r-sm); | |
| padding: 14px; | |
| text-align: center; | |
| } | |
| .rb-val { font-family: var(--font-head); font-size: 22px; font-weight: 800; } | |
| .rb-label { font-size: 11px; color: var(--text2); margin-top: 4px; text-transform: uppercase; letter-spacing: 0.06em; } | |
| .volatility-bar-wrap { margin-bottom: 12px; } | |
| .vol-header { display: flex; justify-content: space-between; font-size: 13px; margin-bottom: 5px; } | |
| .vol-name { color: var(--text2); font-weight: 600; } | |
| .score-ring { | |
| width: 80px; height: 80px; | |
| border-radius: 50%; | |
| display: flex; align-items: center; justify-content: center; | |
| font-family: var(--font-head); | |
| font-size: 24px; | |
| font-weight: 800; | |
| margin: 0 auto 12px; | |
| position: relative; | |
| } | |
| .score-ring::before { | |
| content: ''; | |
| position: absolute; | |
| inset: 4px; | |
| border-radius: 50%; | |
| background: var(--bg2); | |
| } | |
| .score-ring span { position: relative; z-index: 1; } | |
| .scenario-card { | |
| background: var(--bg3); | |
| border-radius: var(--r-sm); | |
| padding: 16px; | |
| border: 1px solid var(--border); | |
| transition: all var(--transition); | |
| } | |
| .scenario-card:hover { border-color: var(--border2); } | |
| .scenario-icon { font-size: 28px; margin-bottom: 8px; } | |
| .scenario-title { font-weight: 700; font-size: 14px; margin-bottom: 6px; } | |
| .scenario-impact { font-size: 13px; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="app-shell"> | |
| <nav class="sidebar"> | |
| <div class="sidebar-logo"> | |
| <div class="logo-mark"> | |
| <div class="logo-icon">📈</div> | |
| <div><div class="logo-text">FinWise</div><div class="logo-sub">Smart Investing</div></div> | |
| </div> | |
| </div> | |
| <div class="nav-section"> | |
| <div class="nav-label">Main</div> | |
| <a href="index.html" class="nav-item"><span class="nav-icon">🏠</span> Dashboard</a> | |
| <a href="portfolio.html" class="nav-item"><span class="nav-icon">📊</span> Portfolio Builder</a> | |
| <a href="risk.html" class="nav-item"><span class="nav-icon">🎯</span> Risk Analyzer</a> | |
| <a href="tracker.html" class="nav-item"><span class="nav-icon">📈</span> Tracker</a> | |
| <div class="nav-label">Tools</div> | |
| <a href="calculators.html" class="nav-item"><span class="nav-icon">🧮</span> Calculators</a> | |
| <a href="insights.html" class="nav-item"><span class="nav-icon">💡</span> Insights</a> | |
| </div> | |
| <div class="sidebar-footer"> | |
| <div class="market-ticker">Live Market</div> | |
| <div id="sidebar-tickers"></div> | |
| </div> | |
| </nav> | |
| <main class="main-content"> | |
| <div class="page-header fade-in"> | |
| <div class="page-title">Risk <span>Analyzer</span></div> | |
| <div class="page-subtitle">Understand and optimize your portfolio's risk profile</div> | |
| </div> | |
| <div class="grid-60-40"> | |
| <!-- Left Column --> | |
| <div> | |
| <!-- Quiz --> | |
| <div class="card fade-in" id="quiz-section"> | |
| <div class="section-title">🧠 Risk Tolerance Assessment</div> | |
| <div class="text-muted text-sm" style="margin-bottom:20px">Answer 5 quick questions — no typing needed</div> | |
| <div id="quiz-container"></div> | |
| <div id="quiz-result" class="hidden" style="margin-top:16px"> | |
| <div style="text-align:center;padding:20px"> | |
| <div style="font-size:48px;margin-bottom:8px" id="result-icon">⚖️</div> | |
| <div style="font-family:var(--font-head);font-size:24px;font-weight:800;margin-bottom:8px" id="result-title">Moderate Investor</div> | |
| <div style="color:var(--text2);font-size:14px;max-width:360px;margin:0 auto" id="result-desc"></div> | |
| </div> | |
| </div> | |
| <div class="flex gap-12" style="margin-top:16px"> | |
| <button class="btn btn-ghost btn-sm" onclick="resetQuiz()">↺ Retake</button> | |
| <button class="btn btn-primary" id="analyze-btn" onclick="runAnalysis()">Analyze My Risk →</button> | |
| </div> | |
| </div> | |
| <!-- Volatility Breakdown --> | |
| <div class="card fade-in fade-in-2" style="margin-top:20px"> | |
| <div class="section-title">📊 Asset Volatility</div> | |
| <div id="volatility-bars"></div> | |
| </div> | |
| <!-- Scenarios --> | |
| <div class="card fade-in fade-in-3" style="margin-top:20px"> | |
| <div class="section-title">🌪️ Scenario Simulation</div> | |
| <div class="text-muted text-sm" style="margin-bottom:16px">How would your portfolio perform in these scenarios?</div> | |
| <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px" id="scenarios-grid"></div> | |
| </div> | |
| </div> | |
| <!-- Right Column --> | |
| <div> | |
| <!-- Risk Gauge --> | |
| <div class="card fade-in-1"> | |
| <div class="card-title">🎯 Risk Score</div> | |
| <div class="gauge-container"> | |
| <svg class="gauge-svg" viewBox="0 0 220 120"> | |
| <!-- Background arcs --> | |
| <path d="M 20 110 A 90 90 0 0 1 200 110" fill="none" stroke="var(--bg3)" stroke-width="18" stroke-linecap="round"/> | |
| <!-- Colored sections --> | |
| <path d="M 20 110 A 90 90 0 0 1 65 29" fill="none" stroke="#10b981" stroke-width="18" stroke-linecap="round" opacity="0.8"/> | |
| <path d="M 65 29 A 90 90 0 0 1 110 20" fill="none" stroke="#22d3ee" stroke-width="18" opacity="0.8"/> | |
| <path d="M 110 20 A 90 90 0 0 1 155 29" fill="none" stroke="#f59e0b" stroke-width="18" opacity="0.8"/> | |
| <path d="M 155 29 A 90 90 0 0 1 200 110" fill="none" stroke="#f43f5e" stroke-width="18" stroke-linecap="round" opacity="0.8"/> | |
| <!-- Needle --> | |
| <g class="gauge-needle" id="gauge-needle" style="transform:rotate(-90deg)"> | |
| <line x1="110" y1="110" x2="110" y2="30" stroke="white" stroke-width="2.5" stroke-linecap="round"/> | |
| <circle cx="110" cy="110" r="6" fill="white"/> | |
| </g> | |
| </svg> | |
| <div class="gauge-center-text"> | |
| <div class="gauge-score" id="gauge-score">—</div> | |
| <div class="gauge-label">Risk Score</div> | |
| </div> | |
| </div> | |
| <div style="text-align:center;margin-top:16px"> | |
| <span class="badge" id="gauge-badge" style="font-size:13px;padding:6px 16px">Calculating...</span> | |
| </div> | |
| <div class="divider"></div> | |
| <div class="risk-breakdown"> | |
| <div class="rb-item"> | |
| <div class="rb-val" id="rb-div" style="color:var(--emerald)">—</div> | |
| <div class="rb-label">Diversification</div> | |
| </div> | |
| <div class="rb-item"> | |
| <div class="rb-val" id="rb-concentrate" style="color:var(--amber)">—</div> | |
| <div class="rb-label">Concentration</div> | |
| </div> | |
| <div class="rb-item"> | |
| <div class="rb-val" id="rb-equity" style="color:var(--violet)">—</div> | |
| <div class="rb-label">Equity %</div> | |
| </div> | |
| <div class="rb-item"> | |
| <div class="rb-val" id="rb-safe" style="color:var(--cyan)">—</div> | |
| <div class="rb-label">Safe Assets</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Rebalancing Suggestions --> | |
| <div class="card fade-in-2" style="margin-top:20px"> | |
| <div class="card-title">⚖️ Rebalancing Suggestions</div> | |
| <div id="rebal-list"> | |
| <div style="color:var(--text2);font-size:13px;text-align:center;padding:20px"> | |
| Complete the risk assessment to see suggestions | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Allocation Type Chart --> | |
| <div class="card fade-in-3" style="margin-top:20px"> | |
| <div class="card-title">🏦 Asset Type Breakdown</div> | |
| <div style="height:180px;position:relative"> | |
| <canvas id="typeChart"></canvas> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| <nav class="bottom-nav"> | |
| <div class="bottom-nav-inner"> | |
| <a href="index.html" class="bottom-nav-item"><span class="bnav-icon">🏠</span>Home</a> | |
| <a href="portfolio.html" class="bottom-nav-item"><span class="bnav-icon">📊</span>Portfolio</a> | |
| <a href="risk.html" class="bottom-nav-item"><span class="bnav-icon">🎯</span>Risk</a> | |
| <a href="tracker.html" class="bottom-nav-item"><span class="bnav-icon">📈</span>Track</a> | |
| <a href="calculators.html" class="bottom-nav-item"><span class="bnav-icon">🧮</span>Calc</a> | |
| <a href="insights.html" class="bottom-nav-item"><span class="bnav-icon">💡</span>Insights</a> | |
| </div> | |
| </nav> | |
| <script src="shared.js"></script> | |
| <script> | |
| const QUESTIONS = [ | |
| { | |
| q: "How long do you plan to keep your investments?", | |
| answers: [ | |
| { text: "⏱️ Less than 1 year", score: 1 }, | |
| { text: "📅 1–3 years", score: 2 }, | |
| { text: "📆 3–10 years", score: 4 }, | |
| { text: "🗓️ 10+ years", score: 5 }, | |
| ] | |
| }, | |
| { | |
| q: "If your portfolio dropped 20% suddenly, you would:", | |
| answers: [ | |
| { text: "😱 Sell everything immediately", score: 1 }, | |
| { text: "😟 Sell some to reduce exposure", score: 2 }, | |
| { text: "😐 Hold and wait for recovery", score: 4 }, | |
| { text: "😏 Buy more at lower prices", score: 5 }, | |
| ] | |
| }, | |
| { | |
| q: "What's your primary investment objective?", | |
| answers: [ | |
| { text: "🛡️ Preserve what I have", score: 1 }, | |
| { text: "🌱 Steady, modest growth", score: 2 }, | |
| { text: "⚖️ Balance growth and safety", score: 3 }, | |
| { text: "🚀 Maximum long-term growth", score: 5 }, | |
| ] | |
| }, | |
| { | |
| q: "What % of your monthly income do you invest?", | |
| answers: [ | |
| { text: "💸 Less than 5%", score: 1 }, | |
| { text: "💰 5–10%", score: 2 }, | |
| { text: "📈 10–25%", score: 4 }, | |
| { text: "🏦 More than 25%", score: 5 }, | |
| ] | |
| }, | |
| { | |
| q: "Which describes your investing experience?", | |
| answers: [ | |
| { text: "🐣 Complete beginner", score: 1 }, | |
| { text: "📖 Read a bit about it", score: 2 }, | |
| { text: "🤓 Have some investments", score: 3 }, | |
| { text: "💹 Active investor / trader", score: 5 }, | |
| ] | |
| }, | |
| ]; | |
| let answers = new Array(QUESTIONS.length).fill(null); | |
| let quizComplete = false; | |
| function renderQuiz() { | |
| const container = document.getElementById('quiz-container'); | |
| container.innerHTML = QUESTIONS.map((q, qi) => ` | |
| <div class="card quiz-card ${answers[qi] === null && qi > 0 && answers[qi-1] === null ? 'inactive' : ''}" | |
| id="quiz-card-${qi}" style="padding:16px;margin-bottom:12px;background:var(--bg3);border:1px solid var(--border)"> | |
| <div class="question-num">Question ${qi+1} of ${QUESTIONS.length}</div> | |
| <div class="question-text">${q.q}</div> | |
| <div class="answer-grid"> | |
| ${q.answers.map((a, ai) => ` | |
| <button class="answer-btn ${answers[qi] === ai ? 'selected' : ''}" | |
| onclick="selectAnswer(${qi}, ${ai})">${a.text}</button> | |
| `).join('')} | |
| </div> | |
| </div> | |
| `).join(''); | |
| } | |
| function selectAnswer(qi, ai) { | |
| answers[qi] = ai; | |
| document.querySelectorAll(`#quiz-card-${qi} .answer-btn`).forEach((btn, i) => { | |
| btn.classList.toggle('selected', i === ai); | |
| }); | |
| const next = document.getElementById('quiz-card-' + (qi+1)); | |
| if (next) next.classList.remove('inactive'); | |
| quizComplete = answers.every(a => a !== null); | |
| if (quizComplete) { | |
| document.getElementById('analyze-btn').style.background = 'linear-gradient(135deg,var(--emerald-d),var(--emerald))'; | |
| } | |
| } | |
| function resetQuiz() { | |
| answers.fill(null); | |
| quizComplete = false; | |
| renderQuiz(); | |
| document.getElementById('quiz-result').classList.add('hidden'); | |
| } | |
| function getTotalScore() { | |
| return answers.reduce((s, ai, qi) => s + (ai !== null ? QUESTIONS[qi].answers[ai].score : 0), 0); | |
| } | |
| const RISK_RESULTS = [ | |
| null, | |
| { icon:'🐢', title:'Very Conservative', desc:'You prefer capital safety above all. Bonds and stable assets suit you best. Low risk, low return.', color:'var(--emerald)' }, | |
| { icon:'🛡️', title:'Conservative', desc:'Modest growth with downside protection. A mix of bonds and blue-chip stocks works for you.', color:'var(--cyan)' }, | |
| { icon:'⚖️', title:'Moderate', desc:'Balanced approach. You accept some volatility for reasonable long-term gains.', color:'var(--amber)' }, | |
| { icon:'🚀', title:'Aggressive', desc:'Growth-focused investor. You can stomach volatility and aim for superior long-term returns.', color:'var(--rose)' }, | |
| { icon:'🦁', title:'Very Aggressive', desc:'Maximum growth seeker. High risk, high reward. Concentrated in equities and high-growth assets.', color:'var(--rose)' }, | |
| ]; | |
| function getProfileFromScore(score) { | |
| if (score <= 8) return RISK_RESULTS[1]; | |
| if (score <= 12) return RISK_RESULTS[2]; | |
| if (score <= 16) return RISK_RESULTS[3]; | |
| if (score <= 20) return RISK_RESULTS[4]; | |
| return RISK_RESULTS[5]; | |
| } | |
| function runAnalysis() { | |
| if (!quizComplete) { showToast('Please answer all 5 questions first', 'error'); return; } | |
| const score = getTotalScore(); | |
| const profile = getProfileFromScore(score); | |
| document.getElementById('quiz-result').classList.remove('hidden'); | |
| document.getElementById('result-icon').textContent = profile.icon; | |
| document.getElementById('result-title').textContent = profile.title; | |
| document.getElementById('result-desc').textContent = profile.desc; | |
| const portfolio = getPortfolio(); | |
| const riskScore = calcRiskScore(portfolio); | |
| const divScore = calcDiversification(portfolio); | |
| // Gauge | |
| const deg = -90 + (riskScore / 100) * 180; | |
| document.getElementById('gauge-needle').style.transform = `rotate(${deg}deg)`; | |
| document.getElementById('gauge-score').textContent = riskScore; | |
| const riskColors = ['badge-emerald','badge-cyan','badge-amber','badge-amber','badge-rose']; | |
| const riskNames = ['Low Risk','Moderate-Low','Moderate','Moderate-High','High Risk']; | |
| const rIdx = Math.floor(riskScore / 25); | |
| const badge = document.getElementById('gauge-badge'); | |
| badge.className = 'badge ' + riskColors[rIdx]; | |
| badge.textContent = riskNames[rIdx]; | |
| // Breakdown | |
| const equityPct = portfolio.assets.filter(a=>a.type==='Stock'||a.type==='ETF').reduce((s,a)=>s+a.pct,0); | |
| const safePct = portfolio.assets.filter(a=>a.type==='Bond').reduce((s,a)=>s+a.pct,0); | |
| const maxPct = Math.max(...portfolio.assets.map(a=>a.pct)); | |
| document.getElementById('rb-div').textContent = divScore + '%'; | |
| document.getElementById('rb-concentrate').textContent = maxPct + '%'; | |
| document.getElementById('rb-equity').textContent = equityPct + '%'; | |
| document.getElementById('rb-safe').textContent = safePct + '%'; | |
| renderRebalSuggestions(portfolio, riskScore, equityPct); | |
| renderVolatility(portfolio); | |
| renderScenarios(portfolio); | |
| renderTypeChart(portfolio); | |
| } | |
| function renderRebalSuggestions(portfolio, riskScore, equityPct) { | |
| const list = document.getElementById('rebal-list'); | |
| const suggestions = []; | |
| const maxAsset = portfolio.assets.reduce((m, a) => a.pct > m.pct ? a : m, portfolio.assets[0]); | |
| if (maxAsset.pct > 35) suggestions.push({ | |
| type:'REDUCE', color:'var(--rose)', | |
| action: `Reduce ${maxAsset.ticker}`, | |
| desc: `${maxAsset.ticker} is ${maxAsset.pct}% of your portfolio — consider trimming to under 30% for better balance.`, | |
| arrow: '↓' | |
| }); | |
| const bondPct = portfolio.assets.filter(a=>a.type==='Bond').reduce((s,a)=>s+a.pct,0); | |
| if (bondPct < 10 && riskScore < 60) suggestions.push({ | |
| type:'ADD', color:'var(--emerald)', | |
| action: 'Add Bonds (BND)', | |
| desc: 'Your portfolio has little fixed income. Adding 10-15% bonds reduces volatility significantly.', | |
| arrow: '+' | |
| }); | |
| if (equityPct > 85) suggestions.push({ | |
| type:'DIVERSIFY', color:'var(--amber)', | |
| action: 'Add Alternative Assets', | |
| desc: 'Over 85% in equities. Consider Gold (GLD) or REITs for non-correlated returns.', | |
| arrow: '↗' | |
| }); | |
| const hasIntl = portfolio.assets.some(a=>a.ticker==='VEA'||a.ticker==='VXUS'); | |
| if (!hasIntl) suggestions.push({ | |
| type:'CONSIDER', color:'var(--cyan)', | |
| action: 'Consider International Exposure', | |
| desc: 'Adding 10-15% international ETFs (VEA, VXUS) can reduce US-market concentration risk.', | |
| arrow: '🌍' | |
| }); | |
| if (suggestions.length === 0) suggestions.push({ | |
| type:'OK', color:'var(--emerald)', | |
| action: '✅ Portfolio looks healthy!', | |
| desc: 'Your diversification and risk levels are well-balanced. Keep up with regular contributions.', | |
| arrow: '👍' | |
| }); | |
| list.innerHTML = suggestions.map(s => ` | |
| <div class="rebal-item"> | |
| <div> | |
| <div class="rebal-action" style="color:${s.color}">${s.type}</div> | |
| <div style="font-weight:700;font-size:14px;margin-bottom:4px">${s.action}</div> | |
| <div class="rebal-desc">${s.desc}</div> | |
| </div> | |
| <div class="rebal-arrow">${s.arrow}</div> | |
| </div> | |
| `).join(''); | |
| } | |
| const VOLATILITY_DATA = { | |
| 'VOO': { vol: 15, beta: 1.00, color: '#22d3ee' }, | |
| 'QQQ': { vol: 22, beta: 1.18, color: '#8b5cf6' }, | |
| 'NVDA': { vol: 48, beta: 1.85, color: '#10b981' }, | |
| 'AAPL': { vol: 25, beta: 1.22, color: '#f59e0b' }, | |
| 'BND': { vol: 4, beta: 0.10, color: '#6366f1' }, | |
| 'GLD': { vol: 12, beta: 0.05, color: '#fbbf24' }, | |
| 'AMZN': { vol: 30, beta: 1.35, color: '#0ea5e9' }, | |
| 'VTI': { vol: 15, beta: 1.00, color: '#34d399' }, | |
| 'TSLA': { vol: 62, beta: 2.10, color: '#f43f5e' }, | |
| 'WMT': { vol: 8, beta: 0.55, color: '#34d399' }, | |
| 'MCD': { vol: 9, beta: 0.70, color: '#fb923c' }, | |
| }; | |
| function renderVolatility(portfolio) { | |
| const container = document.getElementById('volatility-bars'); | |
| container.innerHTML = portfolio.assets.map(a => { | |
| const vdata = VOLATILITY_DATA[a.ticker] || { vol: 20, beta: 1.0, color: '#22d3ee' }; | |
| const volColor = vdata.vol < 10 ? '#10b981' : vdata.vol < 25 ? '#f59e0b' : '#f43f5e'; | |
| return ` | |
| <div class="volatility-bar-wrap"> | |
| <div class="vol-header"> | |
| <span class="vol-name">${a.ticker} <span style="color:var(--text3);font-weight:400;font-size:11px">— ${a.name || ''}</span></span> | |
| <span style="color:${volColor};font-family:var(--font-mono);font-size:12px">±${vdata.vol}% vol · β ${vdata.beta}</span> | |
| </div> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" style="width:${Math.min(vdata.vol*1.2,100)}%;background:${volColor}"></div> | |
| </div> | |
| </div> | |
| `; | |
| }).join(''); | |
| } | |
| function renderScenarios(portfolio) { | |
| const equity = portfolio.assets.filter(a=>a.type==='Stock'||a.type==='ETF').reduce((s,a)=>s+a.pct,0)/100; | |
| const bond = portfolio.assets.filter(a=>a.type==='Bond').reduce((s,a)=>s+a.pct,0)/100; | |
| const gold = portfolio.assets.filter(a=>a.type==='Commodity').reduce((s,a)=>s+a.pct,0)/100; | |
| const scenarios = [ | |
| { icon:'📉', title:'2008 Financial Crisis', impact: ((-38*equity) + (5*bond) + (8*gold)).toFixed(1), desc:'Worst-case equity selloff' }, | |
| { icon:'🦠', title:'2020 COVID Crash', impact: ((-30*equity) + (8*bond) + (5*gold)).toFixed(1), desc:'Fast drop, rapid recovery' }, | |
| { icon:'🔥', title:'Inflation Spike (10%)', impact: ((-15*equity) + (-8*bond) + (20*gold)).toFixed(1), desc:'Rising rates environment' }, | |
| { icon:'🚀', title:'Bull Market (+30%)', impact: ((30*equity) + (3*bond) + (8*gold)).toFixed(1), desc:'Strong economic growth' }, | |
| ]; | |
| document.getElementById('scenarios-grid').innerHTML = scenarios.map(s => { | |
| const isPos = parseFloat(s.impact) >= 0; | |
| return ` | |
| <div class="scenario-card"> | |
| <div class="scenario-icon">${s.icon}</div> | |
| <div class="scenario-title">${s.title}</div> | |
| <div class="scenario-impact" style="color:${isPos?'var(--emerald)':'var(--rose)'};font-family:var(--font-mono);font-size:18px;font-weight:700"> | |
| ${isPos?'+':''}${s.impact}% | |
| </div> | |
| <div style="font-size:11px;color:var(--text2);margin-top:4px">${s.desc}</div> | |
| </div> | |
| `; | |
| }).join(''); | |
| } | |
| function renderTypeChart(portfolio) { | |
| const types = { ETF:0, Stock:0, Bond:0, Commodity:0 }; | |
| portfolio.assets.forEach(a => { types[a.type] = (types[a.type]||0) + a.pct; }); | |
| const ctx = document.getElementById('typeChart').getContext('2d'); | |
| new Chart(ctx, { | |
| type: 'bar', | |
| data: { | |
| labels: Object.keys(types), | |
| datasets: [{ | |
| data: Object.values(types), | |
| backgroundColor: ['rgba(34,211,238,0.7)','rgba(16,185,129,0.7)','rgba(99,102,241,0.7)','rgba(245,158,11,0.7)'], | |
| borderRadius: 6, | |
| borderSkipped: false, | |
| }] | |
| }, | |
| options: { | |
| responsive: true, maintainAspectRatio: false, | |
| plugins: { legend: { display: false } }, | |
| scales: { | |
| x: { grid: { display: false } }, | |
| y: { grid: { color: 'rgba(34,211,238,0.06)' }, ticks: { callback: v => v + '%' } } | |
| } | |
| } | |
| }); | |
| } | |
| document.addEventListener('DOMContentLoaded', () => { | |
| applyChartDefaults(); | |
| renderQuiz(); | |
| // Auto-run analysis from saved portfolio | |
| const portfolio = getPortfolio(); | |
| const riskScore = calcRiskScore(portfolio); | |
| const deg = -90 + (riskScore/100)*180; | |
| setTimeout(() => { | |
| document.getElementById('gauge-needle').style.transform = `rotate(${deg}deg)`; | |
| document.getElementById('gauge-score').textContent = riskScore; | |
| const rIdx = Math.floor(riskScore/25); | |
| const badge = document.getElementById('gauge-badge'); | |
| badge.className = 'badge ' + ['badge-emerald','badge-cyan','badge-amber','badge-amber','badge-rose'][rIdx]; | |
| badge.textContent = ['Low Risk','Moderate-Low','Moderate','Moderate-High','High Risk'][rIdx]; | |
| const equityPct = portfolio.assets.filter(a=>a.type==='Stock'||a.type==='ETF').reduce((s,a)=>s+a.pct,0); | |
| const safePct = portfolio.assets.filter(a=>a.type==='Bond').reduce((s,a)=>s+a.pct,0); | |
| const maxPct = Math.max(...portfolio.assets.map(a=>a.pct)); | |
| document.getElementById('rb-div').textContent = calcDiversification(portfolio) + '%'; | |
| document.getElementById('rb-concentrate').textContent = maxPct + '%'; | |
| document.getElementById('rb-equity').textContent = equityPct + '%'; | |
| document.getElementById('rb-safe').textContent = safePct + '%'; | |
| renderRebalSuggestions(portfolio, riskScore, equityPct); | |
| renderVolatility(portfolio); | |
| renderScenarios(portfolio); | |
| renderTypeChart(portfolio); | |
| }, 400); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |