diff --git a/script.js b/script.js
index f20bfff..970f104 100644
--- a/script.js
+++ b/script.js
@@ -1098,6 +1098,9 @@ class ETFTradeTracker {
// Calculate and display CGT information
this.calculateAndDisplayCGT(etfMap, hasCurrentPrices);
+ // Calculate and display 8+ year CGT information
+ this.calculateAndDisplayLongTermCGT(etfMap, hasCurrentPrices);
+
this.renderGainsBreakdown(etfMap);
}
@@ -1239,6 +1242,162 @@ class ETFTradeTracker {
document.getElementById('cgt-summary').style.display = 'block';
}
+ calculateAndDisplayLongTermCGT(etfMap, hasCurrentPrices) {
+ const longTermSection = document.getElementById('long-term-cgt-section');
+
+ if (!hasCurrentPrices) {
+ longTermSection.style.display = 'none';
+ return;
+ }
+
+ const now = new Date();
+ const eightYearsInDays = 8 * 365.25;
+ const cgtRate = 0.33; // 33% rate for 8+ years
+
+ let eligiblePositions = [];
+ let totalEligibleValue = 0;
+ let totalEligibleGains = 0;
+ let totalCost = 0;
+
+ etfMap.forEach((etf, symbol) => {
+ const currentPrice = this.currentPrices.get(symbol);
+ if (!currentPrice) return;
+
+ // Get all buy trades for this ETF that are 8+ years old
+ const longTermTrades = this.trades
+ .filter(t => t.etfSymbol === symbol && t.tradeType === 'buy')
+ .filter(t => {
+ const tradeDate = new Date(t.dateTime);
+ const holdingDays = Math.floor((now - tradeDate) / (1000 * 60 * 60 * 24));
+ return holdingDays >= eightYearsInDays;
+ })
+ .sort((a, b) => new Date(a.dateTime) - new Date(b.dateTime));
+
+ if (longTermTrades.length === 0) return;
+
+ let positionShares = 0;
+ let positionCost = 0;
+ let oldestTradeDate = null;
+
+ longTermTrades.forEach(trade => {
+ positionShares += trade.shares;
+ positionCost += trade.totalValue;
+ if (!oldestTradeDate || new Date(trade.dateTime) < oldestTradeDate) {
+ oldestTradeDate = new Date(trade.dateTime);
+ }
+ });
+
+ const currentValue = positionShares * currentPrice;
+ const gains = currentValue - positionCost;
+ const holdingDays = Math.floor((now - oldestTradeDate) / (1000 * 60 * 60 * 24));
+ const holdingYears = holdingDays / 365.25;
+
+ if (positionShares > 0) {
+ eligiblePositions.push({
+ symbol,
+ shares: positionShares,
+ cost: positionCost,
+ currentValue,
+ gains,
+ currentPrice,
+ avgCost: positionCost / positionShares,
+ holdingYears,
+ currency: etf.currency
+ });
+
+ totalEligibleValue += currentValue;
+ totalEligibleGains += gains;
+ totalCost += positionCost;
+ }
+ });
+
+ if (eligiblePositions.length === 0) {
+ longTermSection.style.display = 'none';
+ return;
+ }
+
+ // Calculate CGT liability (only on gains)
+ const taxableGains = Math.max(0, totalEligibleGains);
+ const cgtLiability = taxableGains * cgtRate;
+ const afterTaxGains = totalEligibleGains - cgtLiability;
+ const gainsPercentage = totalCost > 0 ? ((totalEligibleGains / totalCost) * 100) : 0;
+
+ // Update display elements
+ document.getElementById('long-term-eligible-value').textContent = this.formatCurrency(totalEligibleValue);
+ document.getElementById('long-term-eligible-count').textContent = `${eligiblePositions.length} position${eligiblePositions.length === 1 ? '' : 's'}`;
+
+ document.getElementById('long-term-total-gains').textContent = this.formatCurrency(totalEligibleGains);
+ document.getElementById('long-term-gains-percentage').textContent = `${gainsPercentage >= 0 ? '+' : ''}${gainsPercentage.toFixed(1)}% gain`;
+
+ document.getElementById('long-term-cgt-liability').textContent = this.formatCurrency(cgtLiability);
+
+ document.getElementById('long-term-after-tax').textContent = this.formatCurrency(afterTaxGains);
+ document.getElementById('long-term-effective-rate').textContent = `Effective rate: ${cgtRate * 100}%`;
+
+ // Render breakdown
+ this.renderLongTermBreakdown(eligiblePositions);
+
+ longTermSection.style.display = 'block';
+ }
+
+ renderLongTermBreakdown(eligiblePositions) {
+ const breakdownList = document.getElementById('long-term-breakdown-list');
+
+ if (eligiblePositions.length === 0) {
+ breakdownList.innerHTML = '
No holdings over 8 years
';
+ return;
+ }
+
+ const breakdownHTML = eligiblePositions.map(position => {
+ const currencySymbol = position.currency === 'EUR' ? '€' : '$';
+ const gainsClass = position.gains >= 0 ? 'positive' : 'negative';
+ const performanceClass = position.gains >= 0 ? 'positive' : 'negative';
+ const gainsPercentage = position.cost > 0 ? ((position.gains / position.cost) * 100) : 0;
+ const cgtLiability = Math.max(0, position.gains) * 0.33;
+ const afterTaxGains = position.gains - cgtLiability;
+
+ return `
+
+
+
+
+ Held
+ ${position.holdingYears.toFixed(1)} years
+
+
+ Shares
+ ${position.shares.toFixed(3)}
+
+
+ Avg Cost
+ ${currencySymbol}${position.avgCost.toFixed(2)}
+
+
+ Current Price
+ ${currencySymbol}${position.currentPrice.toFixed(2)}
+
+
+ CGT (33%)
+ -${currencySymbol}${cgtLiability.toFixed(2)}
+
+
+ After-Tax Gain
+ ${afterTaxGains >= 0 ? '+' : ''}${currencySymbol}${afterTaxGains.toFixed(2)}
+
+
+
+ `;
+ }).join('');
+
+ breakdownList.innerHTML = breakdownHTML;
+ }
+
// CGT Settings and Calculation Methods
async loadCGTSettings() {
try {
diff --git a/styles.css b/styles.css
index 73d23c1..7a87f72 100644
--- a/styles.css
+++ b/styles.css
@@ -1644,4 +1644,158 @@ body {
font-size: 14px;
font-weight: 600;
color: #333;
+}
+
+/* Long-Term CGT Section (8+ Years) */
+.long-term-cgt-section {
+ margin-top: 30px;
+ background: white;
+ padding: 25px;
+ border-radius: 12px;
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+ border-left: 4px solid #17a2b8;
+}
+
+.long-term-cgt-section h3 {
+ margin-bottom: 20px;
+ color: #333;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.long-term-cgt-section h3::before {
+ content: '📅';
+ font-size: 18px;
+}
+
+.long-term-cgt-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: 20px;
+ margin-bottom: 25px;
+}
+
+.long-term-cgt-card {
+ background: #f8f9fa;
+ padding: 20px;
+ border-radius: 8px;
+ border-left: 4px solid #17a2b8;
+ transition: all 0.2s;
+}
+
+.long-term-cgt-card.total-eligible {
+ border-left-color: #6c757d;
+}
+
+.long-term-cgt-card.total-gains-8yr {
+ border-left-color: #28a745;
+ background: #f8fff9;
+}
+
+.long-term-cgt-card.cgt-liability-8yr {
+ border-left-color: #dc3545;
+ background: #fffafa;
+}
+
+.long-term-cgt-card.after-tax-8yr {
+ border-left-color: #007bff;
+ background: #f8f9ff;
+}
+
+.long-term-cgt-card h4 {
+ margin-bottom: 12px;
+ color: #666;
+ font-size: 14px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.long-term-value {
+ font-size: 24px;
+ font-weight: bold;
+ margin-bottom: 8px;
+ color: #28a745;
+}
+
+.long-term-value.negative {
+ color: #dc3545;
+}
+
+.long-term-detail {
+ font-size: 12px;
+ color: #666;
+}
+
+.long-term-breakdown {
+ border-top: 1px solid #e9ecef;
+ padding-top: 20px;
+}
+
+.long-term-breakdown h4 {
+ margin-bottom: 15px;
+ color: #333;
+ font-size: 16px;
+}
+
+.long-term-breakdown-list {
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+}
+
+.long-term-breakdown-item {
+ background: #f8f9fa;
+ padding: 15px;
+ border-radius: 8px;
+ border-left: 4px solid #17a2b8;
+}
+
+.long-term-breakdown-item.positive {
+ border-left-color: #28a745;
+ background: #f8fff9;
+}
+
+.long-term-breakdown-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 10px;
+}
+
+.long-term-symbol {
+ font-weight: bold;
+ font-size: 16px;
+ color: #333;
+}
+
+.long-term-performance {
+ font-weight: bold;
+ color: #28a745;
+}
+
+.long-term-performance.negative {
+ color: #dc3545;
+}
+
+.long-term-details {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
+ gap: 10px;
+}
+
+.long-term-stat {
+ display: flex;
+ justify-content: space-between;
+ font-size: 12px;
+}
+
+.long-term-stat span:first-child {
+ color: #666;
+}
+
+.long-term-stat span:last-child {
+ font-weight: 600;
+ color: #333;
}
\ No newline at end of file