Dynamic Asset Allocation Optimizer

Your Investment Profile

Your investment horizon can influence the suitability of certain asset allocations.

Your Current Assets

Total Current Portfolio Value: $0.00

Your Market Outlooks

Provide your short-to-medium term outlook for these major asset categories. This will influence the dynamic optimization.

Asset Category Your Outlook

Optimized Allocation & Rebalancing Actions

To achieve the optimized allocation, you would need to adjust your portfolio. Detailed rebalancing would involve selling specific current assets and buying into assets that align with the recommended categories.

'; rebalancingHtml += '

This tool recommends target allocations for broad asset categories. You will need to determine how your specific holdings fit these categories and rebalance accordingly.

'; if(this.userAssets.length > 0 && this.totalCurrentValue > 0) { rebalancingHtml += '

Consider the overall value of your current holdings when reallocating towards these target percentages for the broad asset categories.

'; } this.optimizedResultsContainerEl.innerHTML += `
${rebalancingHtml}
`; }, showTab: function(tabIndex) { // Ensure tabs and tabButtons NodeLists are populated and have length if (!this.tabs || this.tabs.length === 0 || !this.tabButtons || this.tabButtons.length === 0) { console.error("DAA Optimizer Error: Tabs or tabButtons not initialized correctly in showTab."); return; } if (tabIndex < 0 || tabIndex >= this.tabs.length) { console.warn("DAA Optimizer Warning: Invalid tabIndex in showTab:", tabIndex); return; } // Validations before moving tabs if (this.currentTab === 1 && tabIndex > 1) { if (this.userAssets.length === 0 || this.userAssets.every(asset => (asset.value || 0) === 0 && !asset.name.trim())) { alert("Please add at least one asset with a name or current value before proceeding from 'Current Portfolio' tab."); return; } if (this.userAssets.some(asset => !asset.name.trim() && (asset.value || 0) > 0)) { alert("Please ensure all asset classes with values have a name."); 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 === 3) { if (this.optimizedResultsContainerEl) this.renderOptimizedResults(); // Check element if (this.pdfButtonContainerEl) this.pdfButtonContainerEl.style.display = 'block'; } else { if (this.pdfButtonContainerEl) this.pdfButtonContainerEl.style.display = 'none'; if (this.optimizationRationaleEl) this.optimizationRationaleEl.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; // Ensure value is a number return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' }); }, generatePdf: function() { if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('The jsPDF library (jspdf.jsPDF) is not loaded correctly.'); console.error('Error: window.jspdf or window.jspdf.jsPDF is not defined.'); return; } const JsPDFConstructor = window.jspdf.jsPDF; let doc; try { doc = new JsPDFConstructor(); } catch (e) { alert('Failed to create a PDF document instance.'); console.error('Error instantiating jsPDF:', e); return; } if (typeof doc.autoTable !== 'function') { alert('The jsPDF-AutoTable plugin is not loaded correctly.'); console.error('Error: doc.autoTable is not a function.'); return; } if (!this.riskToleranceEl || !this.investmentHorizonEl || !this.optimizationRationaleEl) { alert("Cannot generate PDF due to missing critical elements for profile or rationale."); return; } doc.setFontSize(18); doc.text("Dynamic Asset Allocation Optimization 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: ${this.riskToleranceEl.options[this.riskToleranceEl.selectedIndex].text}`, 14, yPos); yPos += 6; doc.text(`Investment Horizon: ${this.investmentHorizonEl.options[this.investmentHorizonEl.selectedIndex].text}`, 14, yPos); yPos += 10; doc.setFontSize(14); doc.text("Market Outlooks", 14, yPos); yPos += 2; const outlooksData = this.assetCategories.map(cat => [cat.name, cat.outlook.replace(/_/g,' ').replace(/\b\w/g, l => l.toUpperCase())]); doc.autoTable({ startY: yPos, head: [['Asset Category', 'Outlook']], body: outlooksData, theme: 'striped', headStyles: { fillColor: this.getCssVariableValue('--primary-color-daa') || [0, 86, 179] } }); yPos = doc.lastAutoTable.finalY + 10; if (this.userAssets.length > 0 && this.totalCurrentValue > 0) { doc.setFontSize(14); doc.text("Current Portfolio Summary", 14, yPos); yPos += 2; const currentPortfolioData = this.userAssets .filter(asset => asset.name.trim() || (asset.value || 0) > 0) // Filter out empty assets .map(asset => [ asset.name || 'N/A', this.formatCurrency(asset.value || 0), `${(asset.currentPercent || 0).toFixed(2)}%` ]); if (currentPortfolioData.length > 0) { doc.autoTable({ startY: yPos, head: [['Asset', 'Current Value', 'Current %']], body: currentPortfolioData }); yPos = doc.lastAutoTable.finalY + 6; } doc.setFontSize(10); doc.text(`Total Current Portfolio Value: ${this.formatCurrency(this.totalCurrentValue)}`, 14, yPos); yPos += 10; } doc.setFontSize(14); doc.text("Recommended Dynamic Allocation", 14, yPos); yPos += 7; const optimizedAllocations = this.getOptimizedAllocation(); const optimizedData = optimizedAllocations.map(cat => [ cat.name, `${((cat.targetPercent || 0) * 100).toFixed(2)}%`, this.formatCurrency(this.totalCurrentValue * (cat.targetPercent || 0)) ]); doc.autoTable({ startY: yPos, head: [['Asset Category', 'Recommended %', 'Recommended Value ($)']], body: optimizedData, headStyles: { fillColor: this.getCssVariableValue('--primary-color-daa') || [0, 86, 179] } }); yPos = doc.lastAutoTable.finalY + 10; doc.setFontSize(11); doc.text("Optimization Rationale:", 14, yPos); yPos +=6; doc.setFontSize(9); const rationaleLines = doc.splitTextToSize(this.optimizationRationaleEl.textContent || "No rationale generated.", 180); doc.text(rationaleLines, 14, yPos); yPos += rationaleLines.length * 4 + 5; doc.setFontSize(9); doc.setTextColor(150); const disclaimer = "This report is based on user-provided data and simplified optimization models. Consult a financial advisor for personalized advice."; const disclaimerLines = doc.splitTextToSize(disclaimer, 180); if (yPos > 260) { doc.addPage(); yPos = 20; } // Check if enough space for disclaimer doc.text(disclaimerLines, 14, yPos); doc.save(`Dynamic_Asset_Allocation_Optimizer_${new Date().toISOString().slice(0,10)}.pdf`); }, getCssVariableValue(variableName) { // Helper to get CSS var for PDF colors try { const colorStr = getComputedStyle(document.documentElement).getPropertyValue(variableName).trim(); if (colorStr.startsWith('#')) { // Convert hex to RGB array 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')) { // Parse rgb(r, g, b) return colorStr.match(/\d+/g).map(Number); } } catch (e) { console.warn("Could not parse CSS variable for PDF:", variableName, e); } return null; // Fallback color } }; daaApp.init(); window.daaApp = daaApp; });
Scroll to Top