Define Your Investment Themes and Their Portfolio Allocation
Enter each thematic area, its overall percentage of your total portfolio, and then break down its allocation into specific assets/ETFs.
All percentages should sum to 100% within their respective scopes.
Please ensure all percentage allocations sum to 100% within their respective themes, and total portfolio theme allocations also sum to 100%.
Overall Portfolio Allocation Summary
Allocation by Investment Theme
Investment Theme
Portfolio Allocation (%)
Total Portfolio Theme Allocation: 0.00%
Allocation by Individual Asset/ETF
Asset Name / Ticker
Investment Theme
Overall Portfolio Allocation (%)
Total Individual Asset Allocation: 0.00%
Define assets within this theme, and their share of this theme (should sum to 100%).
`;
themesContainer.appendChild(themeGroup);
themeGroup.querySelector('.remove-theme-button').addEventListener('click', function() {
themeGroup.remove();
// Recalculate if on results tab
if (activeTab === 'results') {
calculateAllocations();
}
});
themeGroup.querySelector('.add-asset-to-theme-button').addEventListener('click', function() {
const currentThemeId = this.dataset.themeId;
currentAssetIdCounter++;
createAssetInputRow(currentThemeId, currentAssetIdCounter);
});
}
addThemeButton.addEventListener('click', function() {
currentThemeId++;
createThemeInputGroup(currentThemeId);
});
// Add default example themes and assets
createThemeInputGroup(++currentThemeId, 'Artificial Intelligence', '40');
createAssetInputRow(currentThemeId, ++currentAssetIdCounter, 'NVIDIA', '50');
createAssetInputRow(currentThemeId, ++currentAssetIdCounter, 'Microsoft', '30');
createAssetInputRow(currentThemeId, ++currentAssetIdCounter, 'ARKF ETF', '20');
createThemeInputGroup(++currentThemeId, 'Renewable Energy', '30');
createAssetInputRow(currentThemeId, ++currentAssetIdCounter, 'ENPH', '40');
createAssetInputRow(currentThemeId, ++currentAssetIdCounter, 'ICLN ETF', '60');
createThemeInputGroup(++currentThemeId, 'Digital Health', '30');
createAssetInputRow(currentThemeId, ++currentAssetIdCounter, 'TDOC', '35');
createAssetInputRow(currentThemeId, ++currentAssetIdCounter, 'LH', '35');
createAssetInputRow(currentThemeId, ++currentAssetIdCounter, 'Global X Telemedicine & Digital Health ETF', '30');
// --- Allocation Calculation Logic ---
function calculateAllocations() {
const themesData = [];
let totalPortfolioThemeShare = 0;
let overallAssetAllocationSum = 0;
let validationErrors = [];
const themeGroups = themesContainer.querySelectorAll('.theme-input-group');
if (themeGroups.length === 0) {
errorMessage.style.display = 'block';
errorMessage.textContent = 'Please add at least one investment theme.';
themeAllocationTableBody.innerHTML = '
No themes defined.
';
assetAllocationTableBody.innerHTML = '
No assets defined.
';
totalThemeAllocationSpan.textContent = '0.00%';
totalAssetAllocationSpan.textContent = '0.00%';
return;
}
themeGroups.forEach(themeGroup => {
const themeId = themeGroup.dataset.themeId;
const themeName = document.getElementById(`themeNameInput${themeId}`).value.trim();
const themePortfolioShare = parseFloat(document.getElementById(`themePortfolioShare${themeId}`).value);
if (!themeName) {
validationErrors.push(`Theme name for Theme ${themeId} is missing.`);
themeGroup.style.border = '1px solid red';
} else if (isNaN(themePortfolioShare) || themePortfolioShare < 0) {
validationErrors.push(`Invalid 'Theme\'s Share of Total Portfolio' for "${themeName}".`);
themeGroup.style.border = '1px solid red';
} else {
themeGroup.style.border = '1px solid #e0e0e0'; // Reset border
totalPortfolioThemeShare += themePortfolioShare;
const assetsInTheme = [];
let totalAssetShareInTheme = 0;
const assetRows = themeGroup.querySelectorAll('.asset-row');
if (assetRows.length === 0) {
validationErrors.push(`No assets defined for theme "${themeName}".`);
}
assetRows.forEach(assetRow => {
const assetId = assetRow.dataset.assetId;
const assetName = document.getElementById(`assetName${themeId}-${assetId}`).value.trim();
const assetShare = parseFloat(document.getElementById(`assetShare${themeId}-${assetId}`).value);
if (!assetName) {
validationErrors.push(`Asset name missing in theme "${themeName}".`);
assetRow.style.border = '1px solid red';
} else if (isNaN(assetShare) || assetShare < 0) {
validationErrors.push(`Invalid 'Share in Theme' for asset "${assetName}" in "${themeName}".`);
assetRow.style.border = '1px solid red';
} else {
assetRow.style.border = '1px solid #f9f9f9'; // Reset border
assetsInTheme.push({ name: assetName, share: assetShare });
totalAssetShareInTheme += assetShare;
}
});
if (totalAssetShareInTheme > 0 && Math.abs(totalAssetShareInTheme - 100) > 0.01) { // Allow for small floating point errors
validationErrors.push(`Asset shares in theme "${themeName}" do not sum to 100%. Current sum: ${totalAssetShareInTheme.toFixed(1)}%.`);
themeGroup.style.border = '1px solid red';
}
themesData.push({
name: themeName,
portfolioShare: themePortfolioShare,
assets: assetsInTheme,
totalAssetShareInTheme: totalAssetShareInTheme // For validation
});
}
});
if (Math.abs(totalPortfolioThemeShare - 100) > 0.01) {
validationErrors.push(`Total 'Theme\'s Share of Total Portfolio' does not sum to 100%. Current sum: ${totalPortfolioThemeShare.toFixed(1)}%.`);
}
if (validationErrors.length > 0) {
errorMessage.style.display = 'block';
errorMessage.innerHTML = 'Please correct the following issues: ' + validationErrors.map(e => `• ${e}`).join(' ');
// Clear results if there are errors
themeAllocationTableBody.innerHTML = '';
assetAllocationTableBody.innerHTML = '';
totalThemeAllocationSpan.textContent = '0.00%';
totalAssetAllocationSpan.textContent = '0.00%';
return;
} else {
errorMessage.style.display = 'none';
}
// Populate Theme Allocation Table
themeAllocationTableBody.innerHTML = '';
themesData.forEach(theme => {
const row = themeAllocationTableBody.insertRow();
row.innerHTML = `
${theme.name}
${theme.portfolioShare.toFixed(1)}%
`;
});
totalThemeAllocationSpan.textContent = `${totalPortfolioThemeShare.toFixed(1)}%`;
// Populate Asset Allocation Table
assetAllocationTableBody.innerHTML = '';
overallAssetAllocationSum = 0;
const assetSummary = {}; // To aggregate same assets across themes
themesData.forEach(theme => {
if (theme.portfolioShare > 0) {
theme.assets.forEach(asset => {
// Calculate overall portfolio allocation for each asset
const assetOverallAllocation = (theme.portfolioShare / 100) * (asset.share / 100) * 100; // As percentage
// Aggregate if asset appears in multiple themes
if (assetSummary[asset.name]) {
assetSummary[asset.name].allocation += assetOverallAllocation;
} else {
assetSummary[asset.name] = {
theme: theme.name, // Store the first theme for display
allocation: assetOverallAllocation
};
}
overallAssetAllocationSum += assetOverallAllocation;
});
}
});
// Convert aggregated asset summary to an array for display
const sortedAssetNames = Object.keys(assetSummary).sort();
sortedAssetNames.forEach(assetName => {
const data = assetSummary[assetName];
const row = assetAllocationTableBody.insertRow();
row.innerHTML = `