AI-Based Portfolio Optimization Tool

Your Investment Profile

Portfolio Optimization

Based on your profile, our AI will suggest an optimized asset allocation. The available asset classes for this optimization are: US Large Cap Stocks, US Small Cap Stocks, International Developed Market Stocks, Emerging Market Stocks, US Investment Grade Bonds, US High Yield Bonds, Real Estate (REITs), Commodities (e.g., Gold), and Cash/Money Market.

This tool uses the Gemini API. If the environment doesn't provide a key, you'll need to enter one.

Your AI-Optimized Portfolio

Please define your profile and run the optimization.

Could not retrieve portfolio allocation. Please try again.

"; d3.select("#aipo_portfolio_chart").selectAll("*").remove(); dlButton.style.display = 'none'; return; } const allocation = aipo_portfolioData.apiResponse.allocation; const rationale = aipo_portfolioData.apiResponse.rationale; const investmentAmount = aipo_portfolioData.inputs.investmentAmount; let allocationHtml = `

AI Suggested Asset Allocation

`; allocationHtml += `
Total Investment: ${aipo_formatCurrency(investmentAmount)}
    `; const chartData = []; let totalPercentage = 0; for (const asset in allocation) { if (Object.hasOwnProperty.call(allocation, asset)) { const percentage = parseFloat(allocation[asset]); if (isNaN(percentage)) { console.warn(`Invalid percentage for ${asset}: ${allocation[asset]}`); allocationHtml += `
  • ${asset}: Invalid Data
  • `; continue; } totalPercentage += percentage; const amount = (percentage / 100) * investmentAmount; allocationHtml += `
  • ${asset}: ${percentage.toFixed(2)}% (${aipo_formatCurrency(amount)})
  • `; if (percentage > 0) { chartData.push({ asset: asset, value: percentage }); } } } allocationHtml += "
"; if (Math.abs(totalPercentage - 100) > 0.1) { allocationHtml += `

Warning: Total allocation is ${totalPercentage.toFixed(2)}%, not 100%. The AI's suggestion may need review.

`; } allocationHtml += `
AI Rationale:

${rationale || "No rationale provided."}

`; outputDiv.innerHTML = allocationHtml; aipo_drawPieChart(chartData); dlButton.style.display = 'block'; portfolioTabLink.disabled = false; portfolioTabLink.click(); } function aipo_drawPieChart(data) { const svgContainer = d3.select("#aipo_portfolio_chart"); svgContainer.selectAll("*").remove(); const containerElement = svgContainer.node(); if (!containerElement) return; const clientRect = containerElement.getBoundingClientRect(); const containerWidth = Math.min(clientRect.width, 400); if (containerWidth <=0) { // If container has no width (e.g. hidden tab) console.warn("Chart container has no width. Cannot draw chart yet."); return; } const width = containerWidth; const height = containerWidth; const margin = 20; const radius = Math.min(width, height) / 2 - margin; const svg = svgContainer .attr("width", width) .attr("height", height) .append("g") .attr("transform", `translate(${width / 2},${height / 2})`); if (data.length === 0 || data.every(d => d.value === 0)) { svg.append("text") .attr("text-anchor", "middle") .attr("dy", "0.35em") .style("font-size", "14px") .style("fill", "#6b7280") .text("No data to display in chart."); return; } const color = d3.scaleOrdinal(d3.schemeCategory10); const pie = d3.pie() .value(d => d.value) .sort(null); const arc = d3.arc() .innerRadius(radius * 0.4) .outerRadius(radius); const path = svg.selectAll("path") .data(pie(data)) .enter().append("path") .attr("d", arc) .attr("fill", d => color(d.data.asset)) .attr("stroke", "white") .style("stroke-width", "2px"); path.append("title") .text(d => `${d.data.asset}: ${d.data.value.toFixed(2)}%`); const labelArc = d3.arc() .outerRadius(radius * 0.8) .innerRadius(radius * 0.8); svg.selectAll('text.label') .data(pie(data)) .enter().append('text') .attr('class', 'label') .attr('transform', d => `translate(${labelArc.centroid(d)})`) .attr('dy', '.35em') .attr('text-anchor', 'middle') .style('font-size', '10px') .style('fill', '#333') .text(d => { return (d.endAngle - d.startAngle) > 0.2 ? `${d.data.value.toFixed(0)}%` : ''; }); } window.aipo_downloadPDF = function() { const { jsPDF } = window.jspdf; if (!jsPDF || !aipo_portfolioData.inputs || !aipo_portfolioData.apiResponse) { alert("PDF library not loaded or no data to export. Please optimize portfolio first."); return; } const doc = new jsPDF(); let yPos = 20; const lineHeight = 7; const indent = 5; const pageMargin = 15; const pageWidth = doc.internal.pageSize.getWidth(); const usableWidth = pageWidth - 2 * pageMargin; doc.setFontSize(18); doc.setTextColor(26, 32, 44); doc.text("AI-Based Portfolio Optimization Summary", pageWidth / 2, yPos, { align: 'center' }); yPos += lineHeight * 2.5; function addSection(title, dataObj, isList = false) { if (yPos > doc.internal.pageSize.getHeight() - pageMargin - 30) { doc.addPage(); yPos = pageMargin; } doc.setFontSize(13); doc.setFont(undefined, 'bold'); doc.setTextColor(79, 70, 229); doc.text(title, pageMargin, yPos); yPos += lineHeight * 1.5; doc.setFontSize(10); doc.setFont(undefined, 'normal'); doc.setTextColor(51,51,51); if (isList) { dataObj.forEach(item => { if (yPos > doc.internal.pageSize.getHeight() - pageMargin - lineHeight) { doc.addPage(); yPos = pageMargin; } doc.text(item, pageMargin + indent, yPos); yPos += lineHeight; }); } else { for (const [key, value] of Object.entries(dataObj)) { if (yPos > doc.internal.pageSize.getHeight() - pageMargin - lineHeight) { doc.addPage(); yPos = pageMargin; } let formattedKey = key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase()); let valStr = String(value); if (typeof value === 'number' && key.toLowerCase().includes('amount')) { valStr = aipo_formatCurrency(value); } doc.text(`${formattedKey}:`, pageMargin + indent, yPos); doc.text(valStr, pageMargin + indent + 65, yPos, {align: 'left', maxWidth: usableWidth - (indent + 65)}); yPos += lineHeight * 1.1; } } yPos += lineHeight * 0.5; } addSection("Investor Profile", { "Investment Amount": aipo_portfolioData.inputs.investmentAmount, "Risk Tolerance": aipo_portfolioData.inputs.riskTolerance, "Investment Goal": aipo_portfolioData.inputs.investmentGoal, "Investment Horizon": aipo_portfolioData.inputs.investmentHorizon }); if (aipo_portfolioData.apiResponse && aipo_portfolioData.apiResponse.allocation) { doc.setFontSize(13); doc.setFont(undefined, 'bold'); doc.setTextColor(79, 70, 229); if (yPos > doc.internal.pageSize.getHeight() - pageMargin - lineHeight*2) { doc.addPage(); yPos = pageMargin; } doc.text("AI Suggested Asset Allocation", pageMargin, yPos); yPos += lineHeight * 1.5; doc.setFontSize(10); doc.setFont(undefined, 'normal'); doc.setTextColor(51,51,51); const allocation = aipo_portfolioData.apiResponse.allocation; const investmentAmount = aipo_portfolioData.inputs.investmentAmount; for (const asset in allocation) { if (Object.hasOwnProperty.call(allocation, asset)) { if (yPos > doc.internal.pageSize.getHeight() - pageMargin - lineHeight) { doc.addPage(); yPos = pageMargin; } const percentage = parseFloat(allocation[asset]); const amount = (percentage / 100) * investmentAmount; const text = `${asset}: ${percentage.toFixed(2)}% (${aipo_formatCurrency(amount)})`; doc.text(text, pageMargin + indent, yPos); yPos += lineHeight; } } yPos += lineHeight * 0.5; if (yPos > doc.internal.pageSize.getHeight() - pageMargin - 20) { doc.addPage(); yPos = pageMargin; } doc.setFontSize(13); doc.setFont(undefined, 'bold'); doc.setTextColor(79, 70, 229); doc.text("AI Rationale:", pageMargin, yPos); yPos += lineHeight * 1.5; doc.setFontSize(10); doc.setFont(undefined, 'normal'); doc.setTextColor(51,51,51); const rationaleLines = doc.splitTextToSize(aipo_portfolioData.apiResponse.rationale || "No rationale provided.", usableWidth - indent); rationaleLines.forEach(line => { if (yPos > doc.internal.pageSize.getHeight() - pageMargin - lineHeight) { doc.addPage(); yPos = pageMargin; } doc.text(line, pageMargin + indent, yPos); yPos += lineHeight; }); } doc.save('AI_Portfolio_Optimization_Summary.pdf'); }
Scroll to Top