Net P/L
$${netPL.toLocaleString('en-US', {maximumFractionDigits:0})}
Net P/L %
${plPercent.toFixed(2)}%
Cash Position
$${portfolio.cash.toLocaleString('en-US', {maximumFractionDigits:0})}
`;
// Performance Chart
const perfCtx = document.getElementById('performance-chart').getContext('2d');
if (charts.performance) charts.performance.destroy();
charts.performance = new Chart(perfCtx, { type: 'line', data: {
labels: ['T-6', 'T-5', 'T-4', 'T-3', 'T-2', 'T-1', 'Now'],
datasets: [
{ label: 'Portfolio', data: analysisResults.portfolioHistory, borderColor: '#4f46e5', tension: 0.1 },
{ label: 'S&P 500 (SPY)', data: analysisResults.spyHistory, borderColor: '#64748b', tension: 0.1, borderDash: [5, 5] },
]
}, options: { responsive: true, maintainAspectRatio: false, scales: { y: { ticks: { callback: v => `$${(v/1000000).toFixed(1)}M` } } } } });
// Allocation Chart
const allocCtx = document.getElementById('allocation-chart').getContext('2d');
if (charts.allocation) charts.allocation.destroy();
charts.allocation = new Chart(allocCtx, { type: 'doughnut', data: {
labels: Object.keys(analysisResults.allocation),
datasets: [{ data: Object.values(analysisResults.allocation), backgroundColor: CHART_COLORS }]
}, options: { responsive: true, maintainAspectRatio: false } });
}
function renderPortfolio() {
const tableContainer = document.getElementById('holdings-table-container');
const rows = Object.entries(portfolio.holdings).map(([ticker, shares]) => {
const data = MOCK_MARKET_DATA[ticker];
const value = shares * data.price;
return `
| ${ticker} | ${shares.toLocaleString()} | $${value.toLocaleString()} | ${((value / analysisResults.holdingsValue) * 100).toFixed(1)}% |
`;
}).join('');
tableContainer.innerHTML = `
| Ticker | Shares | Value | % of Holdings |
${rows}
`;
document.getElementById('trade-ticker').innerHTML = Object.keys(MOCK_MARKET_DATA).filter(t => t !== 'SPY').map(t => `
`).join('');
}
function executeTrade() {
const ticker = document.getElementById('trade-ticker').value;
const type = document.getElementById('trade-type').value;
const shares = parseInt(document.getElementById('trade-shares').value);
if(!ticker || !type || !shares || shares <= 0) {
alert('Please enter a valid trade.');
return;
}
const price = MOCK_MARKET_DATA[ticker].price;
const cost = shares * price;
if (type === 'buy') {
if (portfolio.cash < cost) { alert('Insufficient cash.'); return; }
portfolio.cash -= cost;
portfolio.holdings[ticker] = (portfolio.holdings[ticker] || 0) + shares;
} else { // sell
if (!portfolio.holdings[ticker] || portfolio.holdings[ticker] < shares) { alert('Not enough shares to sell.'); return; }
portfolio.cash += cost;
portfolio.holdings[ticker] -= shares;
if (portfolio.holdings[ticker] === 0) delete portfolio.holdings[ticker];
}
document.getElementById('trade-shares').value = '';
analyzePortfolio();
}
document.getElementById('execute-trade-btn').addEventListener('click', executeTrade);
// --- PDF GENERATION ---
async function generatePdfReport() {
downloadPdfBtn.disabled = true;
downloadPdfBtn.textContent = 'Generating...';
const { totalValue, netPL, holdingsValue, allocation, portfolioHistory, spyHistory } = analysisResults;
const plPercent = (netPL / (totalValue - netPL)) * 100;
const tableRows = Object.entries(portfolio.holdings).map(([ticker, shares]) => {
const value = shares * MOCK_MARKET_DATA[ticker].price;
return `
| ${ticker} | ${MOCK_MARKET_DATA[ticker].name} | ${shares.toLocaleString()} | $${value.toLocaleString()} | ${((value/holdingsValue)*100).toFixed(2)}% |
`;
}).join('');
const reportHtml = `
TOTAL ASSETS
$${totalValue.toLocaleString(undefined, {maximumFractionDigits:0})}
NET P/L
$${netPL.toLocaleString(undefined, {maximumFractionDigits:0})}
NET RETURN %
${plPercent.toFixed(2)}%
Performance vs. S&P 500 (SPY)
Portfolio Allocation
Detailed Holdings
| Ticker | Company | Shares | Market Value | % of Holdings |
${tableRows}
`;
const pdfTemplate = document.getElementById('pdf-template');
pdfTemplate.innerHTML = reportHtml;
pdfTemplate.classList.remove('invisible');
new Chart(document.getElementById('pdf-performance-chart'), { type: 'line', data: charts.performance.data, options: { ...charts.performance.options, animation: { duration: 0 }, maintainAspectRatio: false } });
new Chart(document.getElementById('pdf-allocation-chart'), { type: 'doughnut', data: charts.allocation.data, options: { ...charts.allocation.options, animation: { duration: 0 }, maintainAspectRatio: false } });
setTimeout(async () => {
try {
const { jsPDF } = window.jspdf;
const pdf = new jsPDF({ orientation: 'p', unit: 'pt', format: 'a4' });
const pages = pdfTemplate.querySelectorAll('.pdf-page');
for (let i = 0; i < pages.length; i++) {
const canvas = await html2canvas(pages[i], { scale: 2 });
const imgData = canvas.toDataURL('image/png');
if (i > 0) pdf.addPage();
const pdfWidth = pdf.internal.pageSize.getWidth(), pdfHeight = (canvas.height * pdfWidth) / canvas.width;
pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight);
}
pdf.save('Hedge_Fund_Performance_Report.pdf');
} catch (e) { console.error('PDF Generation Error:', e); } finally {
downloadPdfBtn.disabled = false;
downloadPdfBtn.textContent = 'Download PDF Report';
pdfTemplate.classList.add('invisible');
pdfTemplate.innerHTML = '';
}
}, 500);
}
downloadPdfBtn.addEventListener('click', generatePdfReport);
// --- INITIALIZATION ---
switchTab(0);
analyzePortfolio();
});