// Portfolio Analysis Page // Detailed view of individual agent portfolios const dataLoader = new DataLoader(); let allAgentsData = {}; let currentAgent = null; let allocationChart = null; // Initialize the page async function init() { showLoading(); try { // Load all agents data console.log('Loading all agents data...'); allAgentsData = await dataLoader.loadAllAgentsData(); console.log('Data loaded:', allAgentsData); // Populate agent selector populateAgentSelector(); // Load first agent by default const firstAgent = Object.keys(allAgentsData)[0]; if (firstAgent) { currentAgent = firstAgent; await loadAgentPortfolio(firstAgent); } // Set up event listeners setupEventListeners(); } catch (error) { console.error('Error initializing page:', error); alert('Failed to load portfolio data. Please check console for details.'); } finally { hideLoading(); } } // Populate agent selector dropdown function populateAgentSelector() { const select = document.getElementById('agentSelect'); select.innerHTML = ''; Object.keys(allAgentsData).forEach(agentName => { const option = document.createElement('option'); option.value = agentName; // Use text only for dropdown options (HTML select doesn't support images well) option.textContent = dataLoader.getAgentDisplayName(agentName); select.appendChild(option); }); } // Load and display portfolio for selected agent async function loadAgentPortfolio(agentName) { showLoading(); try { currentAgent = agentName; const data = allAgentsData[agentName]; // Update performance metrics updateMetrics(data); // Update holdings table await updateHoldingsTable(agentName); // Update allocation chart await updateAllocationChart(agentName); // Update trade history updateTradeHistory(agentName); } catch (error) { console.error('Error loading portfolio:', error); } finally { hideLoading(); } } // Update performance metrics function updateMetrics(data) { const totalAsset = data.currentValue; const totalReturn = data.return; const latestPosition = data.positions && data.positions.length > 0 ? data.positions[data.positions.length - 1] : null; const cashPosition = latestPosition && latestPosition.positions ? latestPosition.positions.CASH || 0 : 0; const totalTrades = data.positions ? data.positions.filter(p => p.this_action).length : 0; document.getElementById('totalAsset').textContent = dataLoader.formatCurrency(totalAsset); document.getElementById('totalReturn').textContent = dataLoader.formatPercent(totalReturn); document.getElementById('totalReturn').className = `metric-value ${totalReturn >= 0 ? 'positive' : 'negative'}`; document.getElementById('cashPosition').textContent = dataLoader.formatCurrency(cashPosition); document.getElementById('totalTrades').textContent = totalTrades; } // Update holdings table async function updateHoldingsTable(agentName) { const holdings = dataLoader.getCurrentHoldings(agentName); const tableBody = document.getElementById('holdingsTableBody'); tableBody.innerHTML = ''; if (!holdings) { return; } const data = allAgentsData[agentName]; if (!data || !data.assetHistory || data.assetHistory.length === 0) { return; } const latestDate = data.assetHistory[data.assetHistory.length - 1].date; const totalValue = data.currentValue; // Get all stocks with non-zero holdings const stocks = Object.entries(holdings) .filter(([symbol, shares]) => symbol !== 'CASH' && shares > 0); // Sort by market value (descending) const holdingsData = await Promise.all( stocks.map(async ([symbol, shares]) => { const price = await dataLoader.getClosingPrice(symbol, latestDate); const marketValue = price ? shares * price : 0; return { symbol, shares, price, marketValue }; }) ); holdingsData.sort((a, b) => b.marketValue - a.marketValue); // Create table rows holdingsData.forEach(holding => { const weight = (holding.marketValue / totalValue * 100).toFixed(2); const row = document.createElement('tr'); row.innerHTML = ` ${holding.symbol} ${holding.shares} ${dataLoader.formatCurrency(holding.price || 0)} ${dataLoader.formatCurrency(holding.marketValue)} ${weight}% `; tableBody.appendChild(row); }); // Add cash row if (holdings.CASH > 0) { const cashWeight = (holdings.CASH / totalValue * 100).toFixed(2); const cashRow = document.createElement('tr'); cashRow.innerHTML = ` CASH - - ${dataLoader.formatCurrency(holdings.CASH)} ${cashWeight}% `; tableBody.appendChild(cashRow); } // If no holdings data, show a message if (holdingsData.length === 0 && (!holdings.CASH || holdings.CASH === 0)) { const noDataRow = document.createElement('tr'); noDataRow.innerHTML = ` No holdings data available `; tableBody.appendChild(noDataRow); } } // Update allocation chart (pie chart) async function updateAllocationChart(agentName) { const holdings = dataLoader.getCurrentHoldings(agentName); if (!holdings) return; const data = allAgentsData[agentName]; const latestDate = data.assetHistory[data.assetHistory.length - 1].date; // Calculate market values const allocations = []; for (const [symbol, shares] of Object.entries(holdings)) { if (symbol === 'CASH') { if (shares > 0) { allocations.push({ label: 'CASH', value: shares }); } } else if (shares > 0) { const price = await dataLoader.getClosingPrice(symbol, latestDate); if (price) { allocations.push({ label: symbol, value: shares * price }); } } } // Sort by value and take top 10, combine rest as "Others" allocations.sort((a, b) => b.value - a.value); const topAllocations = allocations.slice(0, 10); const othersValue = allocations.slice(10).reduce((sum, a) => sum + a.value, 0); if (othersValue > 0) { topAllocations.push({ label: 'Others', value: othersValue }); } // Destroy existing chart if (allocationChart) { allocationChart.destroy(); } // Create new chart const ctx = document.getElementById('allocationChart').getContext('2d'); const colors = [ '#00d4ff', '#00ffcc', '#ff006e', '#ffbe0b', '#8338ec', '#3a86ff', '#fb5607', '#06ffa5', '#ff006e', '#ffbe0b', '#8338ec' ]; allocationChart = new Chart(ctx, { type: 'doughnut', data: { labels: topAllocations.map(a => a.label), datasets: [{ data: topAllocations.map(a => a.value), backgroundColor: colors, borderWidth: 2, borderColor: '#1a2238' }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { color: '#a0aec0', padding: 15, font: { size: 12 } } }, tooltip: { backgroundColor: 'rgba(26, 34, 56, 0.95)', titleColor: '#00d4ff', bodyColor: '#fff', borderColor: '#2d3748', borderWidth: 1, padding: 12, callbacks: { label: function(context) { const label = context.label || ''; const value = dataLoader.formatCurrency(context.parsed); const total = context.dataset.data.reduce((sum, v) => sum + v, 0); const percentage = ((context.parsed / total) * 100).toFixed(1); return `${label}: ${value} (${percentage}%)`; } } } } } }); } // Update trade history timeline function updateTradeHistory(agentName) { const trades = dataLoader.getTradeHistory(agentName); const timeline = document.getElementById('tradeTimeline'); timeline.innerHTML = ''; if (trades.length === 0) { timeline.innerHTML = '

No trade history available.

'; return; } // Show latest 20 trades const recentTrades = trades.slice(0, 20); recentTrades.forEach(trade => { const tradeItem = document.createElement('div'); tradeItem.className = 'trade-item'; const icon = trade.action === 'buy' ? '📈' : '📉'; const iconClass = trade.action === 'buy' ? 'buy' : 'sell'; const actionText = trade.action === 'buy' ? 'Bought' : 'Sold'; tradeItem.innerHTML = `
${icon}
${actionText} ${trade.amount} shares of ${trade.symbol}
${trade.date}
`; timeline.appendChild(tradeItem); }); } // Set up event listeners function setupEventListeners() { document.getElementById('agentSelect').addEventListener('change', (e) => { loadAgentPortfolio(e.target.value); }); // Scroll to top button const scrollBtn = document.getElementById('scrollToTop'); window.addEventListener('scroll', () => { if (window.pageYOffset > 300) { scrollBtn.classList.add('visible'); } else { scrollBtn.classList.remove('visible'); } }); scrollBtn.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }); } // Loading overlay controls function showLoading() { document.getElementById('loadingOverlay').classList.remove('hidden'); } function hideLoading() { document.getElementById('loadingOverlay').classList.add('hidden'); } // Initialize on page load window.addEventListener('DOMContentLoaded', init);