Smart Beta Portfolio Builder

Define Your Profile

Select Smart Beta Factors

Choose the smart beta factors you'd like to include in your portfolio. Hover over factor names for a brief description (if tooltips were implemented via JS/CSS - here, descriptions are inline).

Your Smart Beta Portfolio Summary

Risk Tolerance: ${this.riskToleranceEl ? this.riskToleranceEl.options[this.riskToleranceEl.selectedIndex].text : 'N/A'}

`; summaryHtml += `

Investment Horizon: ${this.investmentHorizonEl ? this.investmentHorizonEl.options[this.investmentHorizonEl.selectedIndex].text : 'N/A'}

`; summaryHtml += `

Total Investment Amount: ${this.formatCurrency(this.investmentAmount)}

`; summaryHtml += `

Factor Allocations:

`; summaryHtml += ''; selectedFactorsWithAllocation.forEach(factor => { summaryHtml += ``; }); summaryHtml += '
Smart Beta FactorAllocation (%)Allocated Value ($)
${factor.name} ${factor.allocation.toFixed(2)}% ${this.formatCurrency(this.investmentAmount * (factor.allocation / 100))}
'; summaryHtml += `

Factor Descriptions:

    `; selectedFactorsWithAllocation.forEach(factor => { summaryHtml += `
  • ${factor.name}: ${factor.description}
  • `; }); summaryHtml += '
'; this.portfolioSummaryContainerEl.innerHTML = summaryHtml; }, showTab: function(tabIndex) { if (!this.tabs || this.tabs.length === 0 || !this.tabButtons || this.tabButtons.length === 0) return; if (tabIndex < 0 || tabIndex >= this.tabs.length) return; // Validation before leaving Tab 2 (Factor Selection & Allocation) if (this.currentTab === 1 && tabIndex > 1) { // Moving from Tab 2 to Tab 3 const selectedFactors = this.smartBetaFactors.filter(f => f.selected); if (selectedFactors.length > 0 && !this.validateAndDisplayTotalAllocation()) { alert("Total factor allocation must sum to 100%. Please adjust allocations."); return; // Stay on Tab 2 } if (selectedFactors.length === 0 && this.investmentAmount > 0) { if (!confirm("You haven't selected or allocated to any smart beta factors. Do you want to proceed to the summary?")) { return; } } } this.tabs.forEach(tab => tab.classList.remove('active')); this.tabButtons.forEach(button => button.classList.remove('active')); if(this.tabs[tabIndex]) this.tabs[tabIndex].classList.add('active'); if(this.tabButtons[tabIndex]) this.tabButtons[tabIndex].classList.add('active'); this.currentTab = tabIndex; this.updateNavButtons(); if (tabIndex === 1) { // Entering Factor Selection / Allocation Tab this.toggleAllocationSectionVisibility(); // Ensure visibility based on current selections this.renderAllocationTable(); // Render table if factors already selected } else if (tabIndex === 2) { // Entering Summary Tab this.renderPortfolioSummary(); if(this.pdfButtonContainerEl) this.pdfButtonContainerEl.style.display = 'block'; } else { if(this.pdfButtonContainerEl) this.pdfButtonContainerEl.style.display = 'none'; } }, navigateTabs: function(direction) { const newTabIndex = this.currentTab + direction; this.showTab(newTabIndex); }, updateNavButtons: function() { if(this.prevButtonEl) this.prevButtonEl.style.display = (this.currentTab === 0) ? 'none' : 'inline-block'; if(this.nextButtonEl) this.nextButtonEl.style.display = (this.currentTab === (this.tabs.length - 1) || this.tabs.length === 0) ? 'none' : 'inline-block'; }, formatCurrency: function(value) { if (isNaN(value) || value === null) value = 0; return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' }); }, getCssVariableValue(variableName) { try { const colorStr = getComputedStyle(document.documentElement).getPropertyValue(variableName).trim(); if (colorStr.startsWith('#')) { let hex = colorStr.substring(1); if (hex.length === 3) hex = hex.split('').map(char => char + char).join(''); const bigint = parseInt(hex, 16); return [(bigint >> 16) & 255, (bigint >> 8) & 255, bigint & 255]; } else if (colorStr.startsWith('rgb')) { return colorStr.match(/\d+/g).map(Number); } } catch (e) { console.warn("Could not parse CSS var for PDF:", variableName, e); } return [44, 62, 80]; // Fallback to --primary-color-sbpb if parsing fails }, generatePdf: function() { if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('jsPDF library not loaded.'); console.error('window.jspdf or window.jspdf.jsPDF not defined.'); return; } const JsPDFConstructor = window.jspdf.jsPDF; let doc; try { doc = new JsPDFConstructor(); } catch (e) { alert('Failed to create PDF instance.'); console.error('Error instantiating jsPDF:', e); return; } if (typeof doc.autoTable !== 'function') { alert('jsPDF-AutoTable plugin not loaded.'); console.error('doc.autoTable not a function.'); return; } const riskEl = this.riskToleranceEl; const horizonEl = this.investmentHorizonEl; if (!riskEl || !horizonEl) { alert("Profile elements missing."); return; } doc.setFontSize(18); doc.text("Smart Beta Portfolio Report", 14, 22); doc.setFontSize(11); doc.setTextColor(100); doc.text(`Report Date: ${new Date().toLocaleDateString()}`, 14, 30); let yPos = 40; doc.setFontSize(14); doc.text("Investment Profile", 14, yPos); yPos += 7; doc.setFontSize(10); doc.text(`Risk Tolerance: ${riskEl.options[riskEl.selectedIndex].text}`, 14, yPos); yPos += 6; doc.text(`Investment Horizon: ${horizonEl.options[horizonEl.selectedIndex].text}`, 14, yPos); yPos += 6; doc.text(`Total Investment Amount: ${this.formatCurrency(this.investmentAmount)}`, 14, yPos); yPos += 10; const allocatedFactors = this.smartBetaFactors.filter(f => f.selected && f.allocation > 0); if (allocatedFactors.length > 0) { doc.setFontSize(14); doc.text("Factor Allocations", 14, yPos); yPos += 2; const portfolioData = allocatedFactors.map(f => [ f.name, `${f.allocation.toFixed(2)}%`, this.formatCurrency(this.investmentAmount * (f.allocation / 100)) ]); doc.autoTable({ startY: yPos, head: [['Smart Beta Factor', 'Allocation (%)', 'Allocated Value ($)']], body: portfolioData, theme: 'striped', headStyles: { fillColor: this.getCssVariableValue('--primary-color-sbpb') } }); yPos = doc.lastAutoTable.finalY + 10; doc.setFontSize(14); doc.text("Factor Descriptions", 14, yPos); yPos += 7; doc.setFontSize(9); allocatedFactors.forEach(factor => { if (yPos > 260) { doc.addPage(); yPos = 20; } // Add new page if content overflows doc.setFont(undefined, 'bold'); doc.text(factor.name + ":", 14, yPos); doc.setFont(undefined, 'normal'); const descLines = doc.splitTextToSize(factor.description, 180); doc.text(descLines, 14 + (doc.getStringUnitWidth(factor.name + ": ") * doc.getFontSize() / doc.internal.scaleFactor) + 2 , yPos); // Attempt to align after bold title yPos += (descLines.length * 4) + 3; // Adjust spacing }); } else { doc.setFontSize(10); doc.text("No smart beta factors were allocated in this portfolio.", 14, yPos); yPos += 10; } doc.setFontSize(9); doc.setTextColor(150); const disclaimer = "This report is based on user-provided data and illustrative models. It is not financial advice. Consult a qualified financial advisor for investment decisions."; if (yPos > 265) { doc.addPage(); yPos = 20; } const disclaimerLines = doc.splitTextToSize(disclaimer, 180); doc.text(disclaimerLines, 14, yPos); doc.save(`Smart_Beta_Portfolio_${new Date().toISOString().slice(0,10)}.pdf`); } }; sbpbApp.init(); window.sbpbApp = sbpbApp; // For debugging });
Scroll to Top