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 = '