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

Current portfolio value is $0.00. Please update in Tab 2 to see meaningful optimized values.

'; } const optimizedAllocations = this.getOptimizedAllocation(); let tableHtml = '

Recommended Dynamic Allocation:

'; tableHtml += ''; optimizedAllocations.forEach(cat => { const recommendedValue = this.totalCurrentValue * cat.targetPercent; tableHtml += ``; }); tableHtml += '
Asset CategoryRecommended %Recommended Value ($)
${cat.name} ${(cat.targetPercent * 100).toFixed(2)}% ${this.formatCurrency(recommendedValue)}
'; this.optimizedResultsContainerEl.innerHTML = tableHtml; let rebalancingHtml = '

Rebalancing Insights (Overall Portfolio to Target Categories):

'; rebalancingHtml += '

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