mirror of
https://github.com/Xe138/AI-Trader.git
synced 2026-04-09 20:27:25 -04:00
init update
This commit is contained in:
327
docs/assets/js/portfolio.js
Normal file
327
docs/assets/js/portfolio.js
Normal file
@@ -0,0 +1,327 @@
|
||||
// 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;
|
||||
const icon = dataLoader.getAgentIcon(agentName);
|
||||
option.textContent = `${icon} ${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 = `
|
||||
<td class="symbol">${holding.symbol}</td>
|
||||
<td>${holding.shares}</td>
|
||||
<td>${dataLoader.formatCurrency(holding.price || 0)}</td>
|
||||
<td>${dataLoader.formatCurrency(holding.marketValue)}</td>
|
||||
<td>${weight}%</td>
|
||||
`;
|
||||
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 = `
|
||||
<td class="symbol">CASH</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>${dataLoader.formatCurrency(holdings.CASH)}</td>
|
||||
<td>${cashWeight}%</td>
|
||||
`;
|
||||
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 = `
|
||||
<td colspan="5" style="text-align: center; color: var(--text-muted); padding: 2rem;">
|
||||
No holdings data available
|
||||
</td>
|
||||
`;
|
||||
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 = '<p style="color: var(--text-muted);">No trade history available.</p>';
|
||||
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 = `
|
||||
<div class="trade-icon ${iconClass}">${icon}</div>
|
||||
<div class="trade-details">
|
||||
<div class="trade-action">${actionText} ${trade.amount} shares of ${trade.symbol}</div>
|
||||
<div class="trade-meta">${trade.date}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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);
|
||||
Reference in New Issue
Block a user