Risk-Return Profile Optimizer

Risk-Return Profile Optimizer

Analyze and optimize investment portfolios based on risk and expected return.

Efficient Frontier & Optimal Portfolios

Your Optimized Portfolio

Run the optimization from the 'Data Configuration' tab to see your results.

Portfolio based on Your Risk Tolerance

Adjust the slider to see a portfolio that matches your risk appetite.

Add at least two assets to define correlations.

'; return; } const table = document.createElement('table'); table.className = 'min-w-full text-sm'; let headerHtml = 'Asset'; assets.forEach(asset => { headerHtml += `${asset.name}`; }); headerHtml += ''; table.innerHTML = headerHtml; const tbody = document.createElement('tbody'); assets.forEach((asset1, i) => { const row = document.createElement('tr'); let rowHtml = `${asset1.name}`; assets.forEach((asset2, j) => { if (i === j) { rowHtml += ``; } else if (j < i) { rowHtml += ``; } else { // Default correlations for initial setup let defaultValue = (i === 0 && j === 1) ? -0.2 : (i === 0 && j === 2) ? 0.6 : 0.1; rowHtml += ``; } }); rowHtml += ''; row.innerHTML = rowHtml; tbody.appendChild(row); }); table.appendChild(tbody); correlationMatrixContainer.appendChild(table); }; const addAsset = () => { assets.push({ id: nextAssetId++, name: 'New Asset', return: 5.0, risk: 10.0 }); renderAssets(); }; const removeAsset = (id) => { assets = assets.filter(asset => asset.id !== id); renderAssets(); }; const updateAssetData = (e) => { if (e.target.classList.contains('asset-input')) { const id = parseInt(e.target.dataset.id); const prop = e.target.dataset.prop; const value = (prop === 'name') ? e.target.value : parseFloat(e.target.value); const asset = assets.find(a => a.id === id); if (asset) { asset[prop] = value; // If name changes, re-render matrix to update headers if(prop === 'name') renderCorrelationMatrix(); } } }; const handleOptimizer = () => { // 1. Get user inputs const riskFreeRate = parseFloat(document.getElementById('riskFreeRate').value) / 100; const numSimulations = parseInt(document.getElementById('simulations').value); const returns = assets.map(a => a.return / 100); const risks = assets.map(a => a.risk / 100); const n = assets.length; // Build correlation matrix const correlations = Array(n).fill(0).map(() => Array(n).fill(0)); const inputs = correlationMatrixContainer.querySelectorAll('.correlation-input'); inputs.forEach(input => { const i = parseInt(input.dataset.i); const j = parseInt(input.dataset.j); const value = parseFloat(input.value); correlations[i][j] = value; correlations[j][i] = value; // Symmetric matrix }); for (let i = 0; i < n; i++) correlations[i][i] = 1.0; // 2. Run Monte Carlo Simulation portfolioSimulations = []; for (let i = 0; i < numSimulations; i++) { // Generate random weights that sum to 1 let weights = Array(n).fill(0).map(() => Math.random()); const totalWeight = weights.reduce((sum, w) => sum + w, 0); weights = weights.map(w => w / totalWeight); // Calculate portfolio return const portfolioReturn = weights.reduce((sum, w, j) => sum + w * returns[j], 0); // Calculate portfolio variance let portfolioVariance = 0; for (let j = 0; j < n; j++) { for (let k = 0; k < n; k++) { portfolioVariance += weights[j] * weights[k] * risks[j] * risks[k] * correlations[j][k]; } } const portfolioRisk = Math.sqrt(portfolioVariance); // Calculate Sharpe Ratio const sharpeRatio = (portfolioReturn - riskFreeRate) / portfolioRisk; portfolioSimulations.push({ return: portfolioReturn, risk: portfolioRisk, sharpe: sharpeRatio, weights: weights, }); } // 3. Find the optimal portfolio (max Sharpe ratio) const optimalPortfolio = portfolioSimulations.reduce((best, current) => (current.sharpe > best.sharpe) ? current : best, { sharpe: -Infinity } ); // 4. Update UI displayResults(optimalPortfolio, 'optimized'); updateChart(portfolioSimulations, optimalPortfolio); updateRiskSliderPortfolio(); // Initial update for slider // 5. Switch to dashboard and enable PDF button switchTab('dashboard'); downloadPdfBtn.disabled = false; }; const updateChart = (portfolios, optimalPortfolio) => { const ctx = document.getElementById('efficientFrontierChart').getContext('2d'); if (chartInstance) { chartInstance.destroy(); } const dataPoints = portfolios.map(p => ({ x: p.risk * 100, y: p.return * 100 })); chartInstance = new Chart(ctx, { type: 'scatter', data: { datasets: [{ label: 'Simulated Portfolios', data: dataPoints, backgroundColor: 'rgba(59, 130, 246, 0.2)', borderColor: 'rgba(59, 130, 246, 0.4)', pointRadius: 2, }, { label: 'Optimal Portfolio (Max Sharpe)', data: [{ x: optimalPortfolio.risk * 100, y: optimalPortfolio.return * 100 }], backgroundColor: 'rgba(239, 68, 68, 1)', pointRadius: 6, pointStyle: 'star', },{ label: 'Your Selected Portfolio', data: [], // Will be updated by slider backgroundColor: 'rgba(22, 163, 74, 1)', pointRadius: 6, pointStyle: 'triangle', }] }, options: { responsive: true, maintainAspectRatio: true, scales: { x: { title: { display: true, text: 'Risk (Standard Deviation %)', font: { size: 14 } }, ticks: { callback: value => value.toFixed(1) + '%' } }, y: { title: { display: true, text: 'Expected Return %', font: { size: 14 } }, ticks: { callback: value => value.toFixed(1) + '%' } } }, plugins: { tooltip: { callbacks: { label: function(context) { let label = context.dataset.label || ''; if (label) label += ': '; label += `Return: ${context.parsed.y.toFixed(2)}%, Risk: ${context.parsed.x.toFixed(2)}%`; return label; } } } } } }); }; const displayResults = (portfolio, type) => { if (!portfolio) return; const containerId = type === 'optimized' ? 'optimized-portfolio-summary' : 'risk-selected-portfolio-summary'; const container = document.getElementById(containerId); let weightsHtml = portfolio.weights.map((weight, i) => { return `
${assets[i].name} ${(weight * 100).toFixed(1)}%
`; }).join(''); container.innerHTML = `
Expected Return: ${(portfolio.return * 100).toFixed(2)}%
Portfolio Risk: ${(portfolio.risk * 100).toFixed(2)}%
Sharpe Ratio: ${portfolio.sharpe.toFixed(3)}

Asset Allocation:

${weightsHtml}
`; }; const updateRiskSliderPortfolio = () => { if(portfolioSimulations.length === 0) return; const tolerance = parseInt(riskToleranceSlider.value); riskToleranceValue.textContent = tolerance; // Find min/max risk to map the slider value const minRisk = Math.min(...portfolioSimulations.map(p => p.risk)); const maxRisk = Math.max(...portfolioSimulations.map(p => p.risk)); const targetRisk = minRisk + (tolerance / 100) * (maxRisk - minRisk); // Find the portfolio on the efficient frontier closest to the target risk // First filter for portfolios on the upper edge (the frontier) const frontierPortfolios = portfolioSimulations.reduce((acc, p) => { const existing = acc.find(item => Math.abs(item.risk - p.risk) < 0.001); if (!existing || p.return > existing.return) { return [...acc.filter(item => Math.abs(item.risk - p.risk) >= 0.001), p]; } return acc; }, []); // Now find the one closest to our target risk from the frontier const selectedPortfolio = frontierPortfolios.reduce((closest, current) => { const closestDiff = Math.abs(closest.risk - targetRisk); const currentDiff = Math.abs(current.risk - targetRisk); return currentDiff < closestDiff ? current : closest; }); displayResults(selectedPortfolio, 'risk-selected'); if (chartInstance) { chartInstance.data.datasets[2].data = [{ x: selectedPortfolio.risk * 100, y: selectedPortfolio.return * 100 }]; chartInstance.update(); } }; const handlePdfDownload = () => { const pdfContent = document.getElementById('pdf-content'); const downloadButton = document.getElementById('download-pdf-btn'); // Hide button during capture downloadButton.style.visibility = 'hidden'; html2canvas(pdfContent, { scale: 2, backgroundColor: '#ffffff' }).then(canvas => { const imgData = canvas.toDataURL('image/png'); const { jsPDF } = window.jspdf; // Use page dimensions of A4 paper const pdf = new jsPDF({ orientation: 'landscape', unit: 'pt', format: 'a4' }); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = pdf.internal.pageSize.getHeight(); const canvasWidth = canvas.width; const canvasHeight = canvas.height; const ratio = canvasWidth / canvasHeight; const imgWidth = pdfWidth - 40; // with some margin const imgHeight = imgWidth / ratio; pdf.setFontSize(18); pdf.text("Risk-Return Profile Optimization Report", 20, 30); pdf.addImage(imgData, 'PNG', 20, 50, imgWidth, imgHeight); pdf.save('Risk-Return-Optimizer-Report.pdf'); // Show button again downloadButton.style.visibility = 'visible'; }); }; // --- EVENT LISTENERS --- window.switchTab = switchTab; // Make it globally accessible for onclick window.navigateTabs = navigateTabs; addAssetBtn.addEventListener('click', addAsset); assetTableBody.addEventListener('click', (e) => { if (e.target.classList.contains('remove-asset-btn')) { removeAsset(parseInt(e.target.dataset.id)); } }); assetTableBody.addEventListener('input', updateAssetData); optimizeBtn.addEventListener('click', handleOptimizer); downloadPdfBtn.addEventListener('click', handlePdfDownload); riskToleranceSlider.addEventListener('input', updateRiskSliderPortfolio); // --- INITIALIZATION --- renderAssets(); updateNavButtons(); switchTab('dashboard'); // Start on the dashboard });
Scroll to Top