Multi-Currency Portfolio Performance Analyzer

Simulate and analyze the performance of a multi-currency portfolio.

Simulation Parameters


Portfolio Holdings

Define each currency holding, its initial amount, initial exchange rate against the selected Base Currency, and its daily volatility. (e.g., for EUR, if Base Currency is USD, 'Initial Rate to Base' is EUR/USD rate like 1.10) (e.g., for JPY, if Base Currency is USD, 'Initial Rate to Base' is 1/USDJPY rate like 1/150=0.00666)

Analysis Results

N/A
N/A
N/A
N/A

Final Holdings Breakdown:

Run analysis to see breakdown.

Disclaimer: This is a **simulation tool** for educational purposes only. It **does not** provide live market data, financial advice, or execute real trades. All simulated exchange rates and portfolio performance figures are hypothetical. Real-world currency movements are complex and influenced by countless factors. This tool uses a simplified random walk model for exchange rate simulation and does not account for real-world complexities like spreads, commissions, or slippage. Trading in financial markets carries significant risks, and you could lose money. Always consult with a qualified financial professional before making investment decisions.

Run analysis to see breakdown.

'; // Reset output class for colors if (totalProfitLossOutput) totalProfitLossOutput.classList.remove('profit', 'loss'); if (totalReturnPercentOutput) totalReturnPercentOutput.classList.remove('profit', 'loss'); if (downloadPdfButton) downloadPdfButton.style.display = 'none'; } function hideError() { if (errorMessage) { errorMessage.textContent = ''; errorMessage.style.display = 'none'; } } function addEventListenersToItemRow(rowElement) { const removeButton = rowElement.querySelector('.remove-item-button'); if (removeButton) { removeButton.onclick = function() { if (portfolioItemsContainer.children.length > 1) { // Ensure at least one row remains rowElement.remove(); // Re-read portfolio items after removal to update the global array readPortfolioItemsFromDOM(); } else { displayError('At least one currency holding is required in the portfolio.'); } }; } } function addCurrencyRow() { const newId = nextItemId++; const newRowHtml = `
`; const newRowElement = document.createElement('div'); newRowElement.innerHTML = newRowHtml.trim(); portfolioItemsContainer.appendChild(newRowElement.firstChild); addEventListenersToItemRow(document.getElementById(`portfolioItemRow_${newId}`)); } // Reads all current portfolio items from the DOM and populates the global `portfolioItems` array function readPortfolioItemsFromDOM() { portfolioItems = []; const rows = portfolioItemsContainer.querySelectorAll('.portfolio-item-row'); rows.forEach(row => { const currencyCode = row.querySelector('.currency-code').value; const initialAmount = parseFloat(row.querySelector('.initial-amount').value); const initialRateToBase = parseFloat(row.querySelector('.initial-rate-to-base').value); const dailyVolatilityPercent = parseFloat(row.querySelector('.daily-volatility-percent').value); portfolioItems.push({ code: currencyCode, initialAmount: initialAmount, initialRateToBase: initialRateToBase, currentExchangeRate: initialRateToBase, // Initialize current rate with initial rate dailyVolatilityPercent: dailyVolatilityPercent }); }); return portfolioItems; } function analyzePortfolio() { hideError(); const baseCurrency = portfolioBaseCurrencySelect.value; const numSimulationDays = parseInt(numSimulationDaysInput.value); // Validate global parameters if (isNaN(numSimulationDays) || numSimulationDays < 1 || numSimulationDays > 1095) { displayError('Number of Simulation Days must be between 1 and 1095.'); return; } const currentPortfolioHoldings = readPortfolioItemsFromDOM(); if (currentPortfolioHoldings.length === 0) { displayError('Please add at least one currency holding to your portfolio.'); return; } // Validate each portfolio item for (const item of currentPortfolioHoldings) { if (isNaN(item.initialAmount) || item.initialAmount <= 0) { displayError(`Invalid Initial Amount for ${item.code}. Must be a positive number.`); return; } if (isNaN(item.initialRateToBase) || item.initialRateToBase <= 0) { // Allow 1 for base currency rate, but check for others if (item.code !== baseCurrency) { displayError(`Invalid Initial Rate to Base for ${item.code}. Must be a positive number.`); return; } else if (item.initialRateToBase !== 1) { displayError(`Initial Rate to Base for ${item.code} (Base Currency) must be 1.`); return; } } if (isNaN(item.dailyVolatilityPercent) || item.dailyVolatilityPercent < 0.01 || item.dailyVolatilityPercent > 5) { displayError(`Invalid Daily Volatility for ${item.code}. Must be between 0.01 and 5.`); return; } } // Store initial values for PDF analysisSummaryForPdf.inputs = { portfolioBaseCurrency: baseCurrency, numSimulationDays: numSimulationDays, portfolioHoldings: JSON.parse(JSON.stringify(currentPortfolioHoldings.map(item => ({ code: item.code, initialAmount: item.initialAmount, initialRateToBase: item.initialRateToBase, dailyVolatilityPercent: item.dailyVolatilityPercent })) )) // Deep copy }; let initialPortfolioValueUSD = 0; currentPortfolioHoldings.forEach(item => { let valueInBase = 0; if (item.code === baseCurrency) { valueInBase = item.initialAmount; } else { // Assuming initialRateToBase is already the correct conversion factor (e.g., EUR/USD for EUR, 1/USDJPY for JPY) valueInBase = item.initialAmount * item.initialRateToBase; } initialPortfolioValueUSD += valueInBase; }); // Simulation loop let currentPortfolioValueUSD = initialPortfolioValueUSD; for (let day = 0; day < numSimulationDays; day++) { currentPortfolioHoldings.forEach(item => { if (item.code !== baseCurrency) { const volatility = item.dailyVolatilityPercent / 100; // Simple random walk: price change is proportional to current price const dailyChangeFactor = (Math.random() * 2 - 1) * volatility; item.currentExchangeRate *= (1 + dailyChangeFactor); // Ensure rate doesn't go too low item.currentExchangeRate = Math.max(0.00001, item.currentExchangeRate); } }); // Recalculate total portfolio value for the day let dailyPortfolioValueUSD = 0; currentPortfolioHoldings.forEach(item => { let valueInBase = 0; if (item.code === baseCurrency) { valueInBase = item.initialAmount; } else { valueInBase = item.initialAmount * item.currentExchangeRate; } dailyPortfolioValueUSD += valueInBase; }); currentPortfolioValueUSD = dailyPortfolioValueUSD; } const finalPortfolioValueUSD = currentPortfolioValueUSD; const totalProfitLossUSD = finalPortfolioValueUSD - initialPortfolioValueUSD; const totalReturnPercent = (initialPortfolioValueUSD !== 0) ? (totalProfitLossUSD / initialPortfolioValueUSD * 100) : 0; // Display Results if (initialPortfolioValueOutput) initialPortfolioValueOutput.textContent = `${baseCurrency} ${initialPortfolioValueUSD.toFixed(2)}`; if (finalPortfolioValueOutput) finalPortfolioValueOutput.textContent = `${baseCurrency} ${finalPortfolioValueUSD.toFixed(2)}`; if (totalProfitLossOutput) { totalProfitLossOutput.textContent = `${baseCurrency} ${totalProfitLossUSD.toFixed(2)}`; totalProfitLossOutput.classList.remove('profit', 'loss'); if (totalProfitLossUSD > 0) totalProfitLossOutput.classList.add('profit'); else if (totalProfitLossUSD < 0) totalProfitLossOutput.classList.add('loss'); } if (totalReturnPercentOutput) { totalReturnPercentOutput.textContent = `${totalReturnPercent.toFixed(2)}%`; totalReturnPercentOutput.classList.remove('profit', 'loss'); if (totalReturnPercent > 0) totalReturnPercentOutput.classList.add('profit'); else if (totalReturnPercent < 0) totalReturnPercentOutput.classList.add('loss'); } // Display currency breakdown if (currencyBreakdownOutput) { let breakdownHtml = '

Final Holdings Breakdown:

'; currentPortfolioHoldings.forEach(item => { let finalValueInBase = 0; if (item.code === baseCurrency) { finalValueInBase = item.initialAmount; } else { finalValueInBase = item.initialAmount * item.currentExchangeRate; } breakdownHtml += `
${item.code}: ${item.initialAmount.toFixed(2)} @ ${item.currentExchangeRate.toFixed(5)} ${baseCurrency} ${finalValueInBase.toFixed(2)}
`; }); currencyBreakdownOutput.innerHTML = breakdownHtml; } // Store summary for PDF analysisSummaryForPdf.results = { initialPortfolioValue: initialPortfolioValueUSD.toFixed(2), finalPortfolioValue: finalPortfolioValueUSD.toFixed(2), totalProfitLoss: totalProfitLossUSD.toFixed(2), totalReturnPercent: totalReturnPercent.toFixed(2), finalHoldingsBreakdown: currentPortfolioHoldings.map(item => { let finalValueInBase = 0; if (item.code === baseCurrency) { finalValueInBase = item.initialAmount; } else { finalValueInBase = item.initialAmount * item.currentExchangeRate; } return { code: item.code, initialAmount: item.initialAmount.toFixed(2), finalRate: item.currentExchangeRate.toFixed(5), finalValueInBase: finalValueInBase.toFixed(2) }; }) }; if (downloadPdfButton) downloadPdfButton.style.display = 'block'; } function resetTool() { // Reset main inputs if (portfolioBaseCurrencySelect) portfolioBaseCurrencySelect.value = "USD"; if (numSimulationDaysInput) numSimulationDaysInput.value = "90"; // Clear and re-add initial portfolio item row if (portfolioItemsContainer) { portfolioItemsContainer.innerHTML = `
`; addEventListenersToItemRow(document.getElementById('portfolioItemRow_1')); } nextItemId = 2; // Reset item ID counter // Reset output fields if (initialPortfolioValueOutput) initialPortfolioValueOutput.textContent = "N/A"; if (finalPortfolioValueOutput) finalPortfolioValueOutput.textContent = "N/A"; if (totalProfitLossOutput) totalProfitLossOutput.textContent = "N/A"; if (totalReturnPercentOutput) totalReturnPercentOutput.textContent = "N/A"; if (currencyBreakdownOutput) currencyBreakdownOutput.innerHTML = '

Final Holdings Breakdown:

Run analysis to see breakdown.

'; // Reset output class for colors if (totalProfitLossOutput) totalProfitLossOutput.classList.remove('profit', 'loss'); if (totalReturnPercentOutput) totalReturnPercentOutput.classList.remove('profit', 'loss'); hideError(); if (downloadPdfButton) downloadPdfButton.style.display = 'none'; analysisSummaryForPdf = {}; updateCurrencySpans(); // Reset currency spans } function downloadPdf() { if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('PDF generation library (jsPDF) not loaded. Please ensure you have an active internet connection, or try reloading the page. If the issue persists, your browser or website security settings might be blocking external scripts.'); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF(); const baseCurrency = portfolioBaseCurrencySelect.value; doc.setFontSize(18); doc.text("Multi-Currency Portfolio Performance Analysis (Simulated)", 14, 22); // Input Parameters doc.setFontSize(12); doc.text("Simulation Parameters:", 14, 35); doc.text(`Portfolio Base Currency: ${analysisSummaryForPdf.inputs.portfolioBaseCurrency}`, 14, 45); doc.text(`Number of Simulation Days: ${analysisSummaryForPdf.inputs.numSimulationDays}`, 14, 52); // Portfolio Holdings Table doc.text("Initial Portfolio Holdings:", 14, 65); const holdingsData = analysisSummaryForPdf.inputs.portfolioHoldings.map(item => [ item.code, item.initialAmount, item.initialRateToBase, item.dailyVolatilityPercent ]); doc.autoTable({ startY: 72, head: [['Currency', 'Initial Amount', 'Initial Rate to Base', 'Daily Volatility (%)']], body: holdingsData, theme: 'striped', headStyles: { fillColor: [0, 123, 255], fontSize: 8 }, styles: { fontSize: 7, cellPadding: 2 } }); const finalY = doc.autoTable.previous.finalY + 10; // Results Summary doc.setFontSize(14); doc.text("Analysis Results:", 14, finalY); doc.setFontSize(12); doc.text(`Initial Portfolio Value (${baseCurrency}): ${baseCurrency} ${analysisSummaryForPdf.results.initialPortfolioValue}`, 14, finalY + 10); doc.text(`Final Portfolio Value (${baseCurrency}): ${baseCurrency} ${analysisSummaryForPdf.results.finalPortfolioValue}`, 14, finalY + 17); doc.text(`Total Profit/Loss (${baseCurrency}): ${baseCurrency} ${analysisSummaryForPdf.results.totalProfitLoss}`, 14, finalY + 24); doc.text(`Total Return (%): ${analysisSummaryForPdf.results.totalReturnPercent}%`, 14, finalY + 31); // Final Holdings Breakdown doc.setFontSize(12); doc.text("Final Holdings Breakdown:", 14, finalY + 45); const finalBreakdownData = analysisSummaryForPdf.results.finalHoldingsBreakdown.map(item => [ item.code, item.initialAmount, item.finalRate, `${baseCurrency} ${item.finalValueInBase}` ]); doc.autoTable({ startY: finalY + 52, head: [['Currency', 'Initial Amount', 'Final Rate to Base', `Final Value (${baseCurrency})`]], body: finalBreakdownData, theme: 'striped', headStyles: { fillColor: [0, 123, 255], fontSize: 8 }, styles: { fontSize: 7, cellPadding: 2 } }); doc.save('Multi_Currency_Portfolio_Analysis_Simulated.pdf'); } });
Scroll to Top