Tactical Asset Allocation Analyzer

Enter Your Current Assets

Total Current Portfolio Value: $0.00

Define Target Tactical Allocations

Specify your target allocation percentage for each asset class. The total must sum to 100%.

Total Target Allocation: 0.00%

Allocation Analysis & Rebalancing

Current portfolio value is $0.00. Cannot calculate target values. Please input current values in Tab 1.

'; return; } const table = document.createElement('table'); table.classList.add('taa-table'); const thead = table.createTHead(); const headerRow = thead.insertRow(); const headers = ['Asset Class', 'Current Value', 'Current %', 'Target Value', 'Target %', 'Change ($)', 'Action']; headers.forEach(text => { const th = document.createElement('th'); th.textContent = text; headerRow.appendChild(th); }); const tbody = table.createTBody(); this.assets.forEach(asset => { if (!asset.name && (asset.value === 0 || isNaN(asset.value)) && (asset.targetPercent === 0 || isNaN(asset.targetPercent))) return; const targetValue = this.totalCurrentValue * ((asset.targetPercent || 0) / 100); const currentValue = asset.value || 0; const changeValue = targetValue - currentValue; let action = 'Hold'; if (changeValue > 0.01) action = `Buy ${this.formatCurrency(changeValue)}`; else if (changeValue < -0.01) action = `Sell ${this.formatCurrency(Math.abs(changeValue))}`; const row = tbody.insertRow(); this.addTableCell(row, asset.name || 'N/A', 'Asset Class'); this.addTableCell(row, this.formatCurrency(currentValue), 'Current Value'); this.addTableCell(row, (asset.currentPercent || 0).toFixed(2) + '%', 'Current %'); this.addTableCell(row, this.formatCurrency(targetValue), 'Target Value'); this.addTableCell(row, (asset.targetPercent || 0).toFixed(2) + '%', 'Target %'); this.addTableCell(row, this.formatCurrency(changeValue), 'Change ($)'); this.addTableCell(row, action, 'Action'); }); this.analysisResultsContainer.appendChild(table); const summary = document.createElement('div'); summary.classList.add('taa-totals'); summary.innerHTML = `

Total Current Portfolio Value: ${this.formatCurrency(this.totalCurrentValue)}

Total Target Portfolio Value: ${this.formatCurrency(this.totalCurrentValue)} (based on rebalancing, not new funds)

`; this.analysisResultsContainer.appendChild(summary); }, addTableCell: function(row, text, dataLabel) { const cell = row.insertCell(); cell.textContent = text; cell.setAttribute('data-label', dataLabel); }, formatCurrency: function(value) { if (isNaN(value)) value = 0; return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' }); }, showTab: function(tabIndex) { if (tabIndex < 0 || tabIndex >= this.tabs.length) return; if (this.currentTab === 0 && tabIndex > 0) { if (this.assets.length === 0 || this.assets.every(asset => (asset.value || 0) === 0 && !asset.name)) { alert("Please add at least one asset with a name or current value before proceeding."); return; } if (this.totalCurrentValue === 0 && this.assets.some(a => a.name)) { if (!confirm("Total current portfolio value is $0.00. Do you want to proceed with $0 values? Target values will also be $0.")) { return; } } if (this.assets.some(asset => !asset.name.trim() && (asset.value || 0) > 0)) { alert("Please ensure all asset classes with values have a name."); return; } } if (this.currentTab === 1 && tabIndex > 1) { if (!this.validateTargetSum()) { alert("Total target allocation must be 100%. Please adjust values in Tab 2."); this.targetPercentageErrorEl.style.display = 'block'; return; } this.targetPercentageErrorEl.style.display = 'none'; } this.tabs.forEach(tab => tab.classList.remove('active')); this.tabButtons.forEach(button => button.classList.remove('active')); this.tabs[tabIndex].classList.add('active'); this.tabButtons[tabIndex].classList.add('active'); this.currentTab = tabIndex; this.updateNavButtons(); if (tabIndex === 1) { this.renderTacticalAdjustmentsTab(); } else if (tabIndex === 2) { this.renderAnalysisResultsTab(); } }, navigateTabs: function(direction) { const newTabIndex = this.currentTab + direction; this.showTab(newTabIndex); }, updateNavButtons: function() { this.prevButton.style.display = (this.currentTab === 0) ? 'none' : 'inline-block'; this.nextButton.style.display = (this.currentTab === this.tabs.length - 1) ? 'none' : 'inline-block'; document.getElementById('taaPdfButtonContainer').style.display = (this.currentTab === this.tabs.length - 1) ? 'block' : 'none'; }, // CORRECTED generatePdf function: generatePdf: function() { // 1. Check if the primary jspdf namespace and its jsPDF constructor exist. if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('The jsPDF library (jspdf.jsPDF) is not loaded correctly. Please check internet connection or script linkage.'); console.error('Error: window.jspdf or window.jspdf.jsPDF is not defined.'); return; } // Pre-flight checks from original logic if (this.currentTab !== 2) { alert("Please navigate to the 'Analysis & Results' tab to generate the PDF."); return; } if (!this.validateTargetSum()) { // This check is also inside renderAnalysisResultsTab, good to have here too. alert("Cannot generate PDF. Total target allocation must be 100%. Please adjust values in Tab 2."); return; } // Check if there's anything to print meaningfully const assetsToPrint = this.assets.filter(asset => asset.name || (asset.value || 0) > 0 || (asset.targetPercent || 0) > 0); if (assetsToPrint.length === 0) { alert("No asset data to include in the PDF. Please add some assets in Tab 1."); return; } // 2. Assign the constructor to a local variable (using a distinct name). const JsPDFConstructor = window.jspdf.jsPDF; let doc; // 3. Instantiate the PDF document. try { doc = new JsPDFConstructor(); } catch (e) { alert('Failed to create a PDF document instance. The jsPDF library might be corrupted or incompatible.'); console.error('Error instantiating jsPDF:', e); return; } // 4. Check for the autoTable plugin on the instance. if (typeof doc.autoTable !== 'function') { alert('The jsPDF-AutoTable plugin is not loaded correctly. PDF tables may not be generated. Ensure the plugin script (jspdf.plugin.autotable.min.js) is loaded after the main jsPDF script.'); console.error('Error: doc.autoTable is not a function.'); return; } doc.setFontSize(18); doc.text("Tactical Asset Allocation Analysis", 14, 22); doc.setFontSize(11); doc.setTextColor(100); doc.text(`Report Date: ${new Date().toLocaleDateString()}`, 14, 30); doc.setFontSize(12); doc.text("Summary:", 14, 45); doc.setFontSize(10); doc.text(`Total Current Portfolio Value: ${this.formatCurrency(this.totalCurrentValue)}`, 14, 52); doc.text(`Total Target Portfolio Value: ${this.formatCurrency(this.totalCurrentValue)} (Rebalanced)`, 14, 59); const tableData = assetsToPrint.map(asset => { const currentValue = asset.value || 0; const currentPercent = asset.currentPercent || 0; const targetPercent = asset.targetPercent || 0; const targetValue = this.totalCurrentValue * (targetPercent / 100); const changeValue = targetValue - currentValue; let action = 'Hold'; if (changeValue > 0.01) action = `Buy ${this.formatCurrency(changeValue)}`; else if (changeValue < -0.01) action = `Sell ${this.formatCurrency(Math.abs(changeValue))}`; return [ asset.name || 'N/A', this.formatCurrency(currentValue), currentPercent.toFixed(2) + '%', this.formatCurrency(targetValue), targetPercent.toFixed(2) + '%', this.formatCurrency(changeValue), action ]; }); doc.autoTable({ startY: 68, head: [['Asset Class', 'Current Value', 'Current %', 'Target Value', 'Target %', 'Change ($)', 'Action']], body: tableData, theme: 'striped', headStyles: { fillColor: [0, 123, 255] }, styles: { fontSize: 9, cellPadding: 2 }, columnStyles: { 0: { cellWidth: 35 }, 1: { cellWidth: 25, halign: 'right' }, 2: { cellWidth: 18, halign: 'right' }, 3: { cellWidth: 25, halign: 'right' }, 4: { cellWidth: 18, halign: 'right' }, 5: { cellWidth: 25, halign: 'right' }, 6: { cellWidth: 'auto' } } }); const finalY = doc.lastAutoTable.finalY || 70; doc.setFontSize(10); doc.setTextColor(150); doc.text("This analysis is based on the data provided by the user.", 14, finalY + 10); doc.save(`Tactical_Asset_Allocation_Analysis_${new Date().toISOString().slice(0,10)}.pdf`); } }; app.init(); window.taaApp = app; });
Scroll to Top