Add 8+ Year Long-Term CGT Section with 33% Tax Rate
Features: - New section specifically for holdings over 8 years - Fixed 33% CGT rate for ultra long-term positions - Automatic identification of 8+ year old trades - Comprehensive breakdown showing individual position performance - Visual cards displaying key metrics: - Eligible holdings count and total value - Total gains on 8+ year positions - CGT liability at 33% rate - After-tax gains calculation Calculation Logic: - Identifies all buy trades that are 8+ years old (≥2920 days) - Groups trades by ETF symbol for position-level calculations - Calculates current value vs original cost basis - Applies 33% tax rate only to positive gains - Shows holding period for each position in years - Displays both gross and net (after-tax) performance Visual Design: - Distinct blue color scheme to differentiate from other CGT calculations - Calendar icon (📅) to emphasize long-term nature - Detailed breakdown cards for each qualifying position - Color-coded positive/negative performance indicators - Professional grid layout with responsive design Display Features: - Only appears when current prices are updated - Hidden when no positions qualify (under 8 years) - Shows exact holding period in years for each position - Individual CGT liability and after-tax gains per position - Clear distinction from existing variable-rate CGT system 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
16e77850cd
commit
503addf705
38
index.html
38
index.html
@ -326,6 +326,44 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="long-term-cgt-section" id="long-term-cgt-section" style="display: none;">
|
||||
<h3>8+ Year Holdings - Long-Term CGT (33%)</h3>
|
||||
<div class="long-term-cgt-summary">
|
||||
<div class="long-term-cgt-grid">
|
||||
<div class="long-term-cgt-card total-eligible">
|
||||
<h4>Eligible Holdings (8+ Years)</h4>
|
||||
<div class="long-term-value" id="long-term-eligible-value">€0.00</div>
|
||||
<div class="long-term-detail" id="long-term-eligible-count">0 positions</div>
|
||||
</div>
|
||||
|
||||
<div class="long-term-cgt-card total-gains-8yr">
|
||||
<h4>Total Gains (8+ Years)</h4>
|
||||
<div class="long-term-value" id="long-term-total-gains">€0.00</div>
|
||||
<div class="long-term-detail" id="long-term-gains-percentage">0.0% gain</div>
|
||||
</div>
|
||||
|
||||
<div class="long-term-cgt-card cgt-liability-8yr">
|
||||
<h4>CGT Liability (33%)</h4>
|
||||
<div class="long-term-value negative" id="long-term-cgt-liability">€0.00</div>
|
||||
<div class="long-term-detail">On gains over 8 years</div>
|
||||
</div>
|
||||
|
||||
<div class="long-term-cgt-card after-tax-8yr">
|
||||
<h4>After-Tax Gains</h4>
|
||||
<div class="long-term-value" id="long-term-after-tax">€0.00</div>
|
||||
<div class="long-term-detail" id="long-term-effective-rate">Effective rate: 33%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="long-term-breakdown">
|
||||
<h4>8+ Year Holdings Breakdown</h4>
|
||||
<div id="long-term-breakdown-list" class="long-term-breakdown-list">
|
||||
<p class="no-data">No holdings over 8 years</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gains-breakdown-section">
|
||||
<h3>Individual Performance</h3>
|
||||
<div id="gains-breakdown-list" class="gains-breakdown-list">
|
||||
|
||||
159
script.js
159
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 = '<p class="no-data">No holdings over 8 years</p>';
|
||||
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 `
|
||||
<div class="long-term-breakdown-item ${gainsClass}">
|
||||
<div class="long-term-breakdown-header">
|
||||
<span class="long-term-symbol">${position.symbol}</span>
|
||||
<span class="long-term-performance ${performanceClass}">
|
||||
${position.gains >= 0 ? '+' : ''}${currencySymbol}${position.gains.toFixed(2)}
|
||||
(${position.gains >= 0 ? '+' : ''}${gainsPercentage.toFixed(1)}%)
|
||||
</span>
|
||||
</div>
|
||||
<div class="long-term-details">
|
||||
<div class="long-term-stat">
|
||||
<span>Held</span>
|
||||
<span>${position.holdingYears.toFixed(1)} years</span>
|
||||
</div>
|
||||
<div class="long-term-stat">
|
||||
<span>Shares</span>
|
||||
<span>${position.shares.toFixed(3)}</span>
|
||||
</div>
|
||||
<div class="long-term-stat">
|
||||
<span>Avg Cost</span>
|
||||
<span>${currencySymbol}${position.avgCost.toFixed(2)}</span>
|
||||
</div>
|
||||
<div class="long-term-stat">
|
||||
<span>Current Price</span>
|
||||
<span>${currencySymbol}${position.currentPrice.toFixed(2)}</span>
|
||||
</div>
|
||||
<div class="long-term-stat">
|
||||
<span>CGT (33%)</span>
|
||||
<span>-${currencySymbol}${cgtLiability.toFixed(2)}</span>
|
||||
</div>
|
||||
<div class="long-term-stat">
|
||||
<span>After-Tax Gain</span>
|
||||
<span class="${performanceClass}">${afterTaxGains >= 0 ? '+' : ''}${currencySymbol}${afterTaxGains.toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
breakdownList.innerHTML = breakdownHTML;
|
||||
}
|
||||
|
||||
// CGT Settings and Calculation Methods
|
||||
async loadCGTSettings() {
|
||||
try {
|
||||
|
||||
154
styles.css
154
styles.css
@ -1645,3 +1645,157 @@ body {
|
||||
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;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user