mirror of
https://github.com/Xe138/AI-Trader.git
synced 2026-04-01 17:17:24 -04:00
init update
This commit is contained in:
0
docs/.nojekyll
Normal file
0
docs/.nojekyll
Normal file
BIN
docs/assets/.DS_Store
vendored
Normal file
BIN
docs/assets/.DS_Store
vendored
Normal file
Binary file not shown.
1037
docs/assets/css/styles.css
Normal file
1037
docs/assets/css/styles.css
Normal file
File diff suppressed because it is too large
Load Diff
432
docs/assets/js/asset-chart.js
Normal file
432
docs/assets/js/asset-chart.js
Normal file
@@ -0,0 +1,432 @@
|
||||
// Asset Evolution Chart
|
||||
// Main page visualization
|
||||
|
||||
const dataLoader = new DataLoader();
|
||||
let chartInstance = null;
|
||||
let allAgentsData = {};
|
||||
let isLogScale = false;
|
||||
|
||||
// Color palette for different agents
|
||||
const agentColors = [
|
||||
'#00d4ff', // Cyan Blue
|
||||
'#00ffcc', // Cyan
|
||||
'#ff006e', // Hot Pink
|
||||
'#ffbe0b', // Yellow
|
||||
'#8338ec', // Purple
|
||||
'#3a86ff', // Blue
|
||||
'#fb5607', // Orange
|
||||
'#06ffa5' // Mint
|
||||
];
|
||||
|
||||
// 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);
|
||||
|
||||
// Update stats
|
||||
updateStats();
|
||||
|
||||
// Create chart
|
||||
createChart();
|
||||
|
||||
// Create legend
|
||||
createLegend();
|
||||
|
||||
// Set up event listeners
|
||||
setupEventListeners();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error initializing page:', error);
|
||||
alert('Failed to load trading data. Please check console for details.');
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
// Update statistics cards
|
||||
function updateStats() {
|
||||
const agentNames = Object.keys(allAgentsData);
|
||||
const agentCount = agentNames.length;
|
||||
|
||||
// Calculate date range
|
||||
let minDate = null;
|
||||
let maxDate = null;
|
||||
|
||||
agentNames.forEach(name => {
|
||||
const history = allAgentsData[name].assetHistory;
|
||||
if (history.length > 0) {
|
||||
const firstDate = history[0].date;
|
||||
const lastDate = history[history.length - 1].date;
|
||||
|
||||
if (!minDate || firstDate < minDate) minDate = firstDate;
|
||||
if (!maxDate || lastDate > maxDate) maxDate = lastDate;
|
||||
}
|
||||
});
|
||||
|
||||
// Find best performer
|
||||
let bestAgent = null;
|
||||
let bestReturn = -Infinity;
|
||||
|
||||
agentNames.forEach(name => {
|
||||
const returnValue = allAgentsData[name].return;
|
||||
if (returnValue > bestReturn) {
|
||||
bestReturn = returnValue;
|
||||
bestAgent = name;
|
||||
}
|
||||
});
|
||||
|
||||
// Update DOM
|
||||
document.getElementById('agent-count').textContent = agentCount;
|
||||
document.getElementById('trading-period').textContent = minDate && maxDate ?
|
||||
`${minDate} to ${maxDate}` : 'N/A';
|
||||
document.getElementById('best-performer').textContent = bestAgent ?
|
||||
dataLoader.getAgentDisplayName(bestAgent) : 'N/A';
|
||||
document.getElementById('avg-return').textContent = bestAgent ?
|
||||
dataLoader.formatPercent(bestReturn) : 'N/A';
|
||||
}
|
||||
|
||||
// Create the main chart
|
||||
function createChart() {
|
||||
const ctx = document.getElementById('assetChart').getContext('2d');
|
||||
|
||||
// Collect all unique dates and sort them
|
||||
const allDates = new Set();
|
||||
Object.keys(allAgentsData).forEach(agentName => {
|
||||
allAgentsData[agentName].assetHistory.forEach(h => allDates.add(h.date));
|
||||
});
|
||||
const sortedDates = Array.from(allDates).sort();
|
||||
|
||||
const datasets = Object.keys(allAgentsData).map((agentName, index) => {
|
||||
const data = allAgentsData[agentName];
|
||||
let color, borderWidth, borderDash;
|
||||
|
||||
// Special styling for QQQ benchmark
|
||||
if (agentName === 'QQQ') {
|
||||
color = dataLoader.getAgentBrandColor(agentName) || '#ff6b00';
|
||||
borderWidth = 2;
|
||||
borderDash = [5, 5]; // Dashed line for benchmark
|
||||
} else {
|
||||
color = agentColors[index % agentColors.length];
|
||||
borderWidth = 3;
|
||||
borderDash = [];
|
||||
}
|
||||
|
||||
// Create data points for all dates, filling missing dates with null
|
||||
const chartData = sortedDates.map(date => {
|
||||
const historyEntry = data.assetHistory.find(h => h.date === date);
|
||||
return {
|
||||
x: date,
|
||||
y: historyEntry ? historyEntry.value : null
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
label: dataLoader.getAgentDisplayName(agentName),
|
||||
data: chartData,
|
||||
borderColor: color,
|
||||
backgroundColor: agentName === 'QQQ' ? 'transparent' : createGradient(ctx, color),
|
||||
borderWidth: borderWidth,
|
||||
borderDash: borderDash,
|
||||
tension: 0.42, // Smooth curves for financial charts
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 7,
|
||||
pointHoverBackgroundColor: color,
|
||||
pointHoverBorderColor: '#fff',
|
||||
pointHoverBorderWidth: 3,
|
||||
fill: agentName !== 'QQQ', // No fill for QQQ benchmark
|
||||
agentName: agentName,
|
||||
agentIcon: dataLoader.getAgentIcon(agentName),
|
||||
cubicInterpolationMode: 'monotone' // Smooth, monotonic interpolation
|
||||
};
|
||||
});
|
||||
|
||||
// Create gradient for area fills
|
||||
function createGradient(ctx, color) {
|
||||
// Parse color and create gradient
|
||||
const gradient = ctx.createLinearGradient(0, 0, 0, 400);
|
||||
gradient.addColorStop(0, color + '30'); // 30% opacity at top
|
||||
gradient.addColorStop(0.5, color + '15'); // 15% opacity at middle
|
||||
gradient.addColorStop(1, color + '05'); // 5% opacity at bottom
|
||||
return gradient;
|
||||
}
|
||||
|
||||
// Custom plugin to draw icons on chart lines
|
||||
const iconPlugin = {
|
||||
id: 'iconLabels',
|
||||
afterDatasetsDraw: (chart) => {
|
||||
const ctx = chart.ctx;
|
||||
|
||||
chart.data.datasets.forEach((dataset, datasetIndex) => {
|
||||
const meta = chart.getDatasetMeta(datasetIndex);
|
||||
if (!meta.hidden && dataset.data.length > 0) {
|
||||
// Get the last point
|
||||
const lastPoint = meta.data[meta.data.length - 1];
|
||||
|
||||
if (lastPoint) {
|
||||
const x = lastPoint.x;
|
||||
const y = lastPoint.y;
|
||||
|
||||
// Draw icon
|
||||
ctx.save();
|
||||
ctx.font = 'bold 22px Arial';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
// Draw background circle with glow
|
||||
const iconSize = 30;
|
||||
|
||||
// Outer glow
|
||||
ctx.shadowColor = dataset.borderColor;
|
||||
ctx.shadowBlur = 15;
|
||||
ctx.fillStyle = dataset.borderColor;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x + 22, y, iconSize / 2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// Reset shadow
|
||||
ctx.shadowBlur = 0;
|
||||
|
||||
// Draw icon
|
||||
ctx.fillStyle = '#0a0e27';
|
||||
ctx.fillText(dataset.agentIcon, x + 22 - 9, y);
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
chartInstance = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: { datasets },
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
resizeDelay: 200,
|
||||
layout: {
|
||||
padding: {
|
||||
right: 50,
|
||||
top: 10,
|
||||
bottom: 10
|
||||
}
|
||||
},
|
||||
interaction: {
|
||||
mode: 'index',
|
||||
intersect: false
|
||||
},
|
||||
elements: {
|
||||
line: {
|
||||
borderJoinStyle: 'round',
|
||||
borderCapStyle: 'round'
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(26, 34, 56, 0.95)',
|
||||
titleColor: '#00d4ff',
|
||||
bodyColor: '#fff',
|
||||
borderColor: '#2d3748',
|
||||
borderWidth: 1,
|
||||
padding: 12,
|
||||
displayColors: true,
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
const label = context.dataset.label || '';
|
||||
const value = dataLoader.formatCurrency(context.parsed.y);
|
||||
const icon = context.dataset.agentIcon || '';
|
||||
return `${icon} ${label}: ${value}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: 'category',
|
||||
labels: sortedDates,
|
||||
grid: {
|
||||
color: 'rgba(45, 55, 72, 0.3)',
|
||||
drawBorder: false,
|
||||
lineWidth: 1
|
||||
},
|
||||
ticks: {
|
||||
color: '#a0aec0',
|
||||
maxRotation: 45,
|
||||
minRotation: 45,
|
||||
autoSkip: true,
|
||||
maxTicksLimit: 10,
|
||||
font: {
|
||||
size: 11
|
||||
}
|
||||
}
|
||||
},
|
||||
y: {
|
||||
type: isLogScale ? 'logarithmic' : 'linear',
|
||||
grid: {
|
||||
color: 'rgba(45, 55, 72, 0.3)',
|
||||
drawBorder: false,
|
||||
lineWidth: 1
|
||||
},
|
||||
ticks: {
|
||||
color: '#a0aec0',
|
||||
callback: function(value) {
|
||||
return dataLoader.formatCurrency(value);
|
||||
},
|
||||
font: {
|
||||
size: 11
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [iconPlugin]
|
||||
});
|
||||
}
|
||||
|
||||
// Create legend
|
||||
function createLegend() {
|
||||
const legendContainer = document.getElementById('agentLegend');
|
||||
legendContainer.innerHTML = '';
|
||||
|
||||
Object.keys(allAgentsData).forEach((agentName, index) => {
|
||||
const data = allAgentsData[agentName];
|
||||
let color, borderStyle;
|
||||
|
||||
// Special styling for QQQ benchmark
|
||||
if (agentName === 'QQQ') {
|
||||
color = dataLoader.getAgentBrandColor(agentName) || '#ff6b00';
|
||||
borderStyle = 'dashed';
|
||||
} else {
|
||||
color = agentColors[index % agentColors.length];
|
||||
borderStyle = 'solid';
|
||||
}
|
||||
|
||||
const returnValue = data.return;
|
||||
const returnClass = returnValue >= 0 ? 'positive' : 'negative';
|
||||
const icon = dataLoader.getAgentIcon(agentName);
|
||||
const brandColor = dataLoader.getAgentBrandColor(agentName);
|
||||
|
||||
const legendItem = document.createElement('div');
|
||||
legendItem.className = 'legend-item';
|
||||
legendItem.innerHTML = `
|
||||
<div class="legend-icon" ${brandColor ? `style="background: ${brandColor}20; color: ${brandColor};"` : ''}>${icon}</div>
|
||||
<div class="legend-color" style="background: ${color}; border-style: ${borderStyle};"></div>
|
||||
<div class="legend-info">
|
||||
<div class="legend-name">${dataLoader.getAgentDisplayName(agentName)}</div>
|
||||
<div class="legend-return ${returnClass}">${dataLoader.formatPercent(returnValue)}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
legendContainer.appendChild(legendItem);
|
||||
});
|
||||
}
|
||||
|
||||
// Toggle between linear and log scale
|
||||
function toggleScale() {
|
||||
isLogScale = !isLogScale;
|
||||
|
||||
const button = document.getElementById('toggle-log');
|
||||
button.textContent = isLogScale ? 'Log Scale' : 'Linear Scale';
|
||||
|
||||
// Update chart
|
||||
if (chartInstance) {
|
||||
chartInstance.destroy();
|
||||
}
|
||||
createChart();
|
||||
}
|
||||
|
||||
// Export chart data as CSV
|
||||
function exportData() {
|
||||
let csv = 'Date,';
|
||||
|
||||
// Header row with agent names
|
||||
const agentNames = Object.keys(allAgentsData);
|
||||
csv += agentNames.map(name => dataLoader.getAgentDisplayName(name)).join(',') + '\n';
|
||||
|
||||
// Collect all unique dates
|
||||
const allDates = new Set();
|
||||
agentNames.forEach(name => {
|
||||
allAgentsData[name].assetHistory.forEach(h => allDates.add(h.date));
|
||||
});
|
||||
|
||||
// Sort dates
|
||||
const sortedDates = Array.from(allDates).sort();
|
||||
|
||||
// Data rows
|
||||
sortedDates.forEach(date => {
|
||||
const row = [date];
|
||||
agentNames.forEach(name => {
|
||||
const history = allAgentsData[name].assetHistory;
|
||||
const entry = history.find(h => h.date === date);
|
||||
row.push(entry ? entry.value.toFixed(2) : '');
|
||||
});
|
||||
csv += row.join(',') + '\n';
|
||||
});
|
||||
|
||||
// Download CSV
|
||||
const blob = new Blob([csv], { type: 'text/csv' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'aitrader_asset_evolution.csv';
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// Set up event listeners
|
||||
function setupEventListeners() {
|
||||
document.getElementById('toggle-log').addEventListener('click', toggleScale);
|
||||
document.getElementById('export-chart').addEventListener('click', exportData);
|
||||
|
||||
// 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' });
|
||||
});
|
||||
|
||||
// Window resize handler for chart responsiveness
|
||||
let resizeTimeout;
|
||||
const handleResize = () => {
|
||||
clearTimeout(resizeTimeout);
|
||||
resizeTimeout = setTimeout(() => {
|
||||
if (chartInstance) {
|
||||
console.log('Resizing chart...'); // Debug log
|
||||
chartInstance.resize();
|
||||
chartInstance.update('none'); // Force update without animation
|
||||
}
|
||||
}, 100); // Faster response
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
// Also handle orientation change for mobile
|
||||
window.addEventListener('orientationchange', handleResize);
|
||||
}
|
||||
|
||||
// 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);
|
||||
390
docs/assets/js/data-loader.js
Normal file
390
docs/assets/js/data-loader.js
Normal file
@@ -0,0 +1,390 @@
|
||||
// Data Loader Utility
|
||||
// Handles loading and processing all trading data
|
||||
|
||||
class DataLoader {
|
||||
constructor() {
|
||||
this.agentData = {};
|
||||
this.priceCache = {};
|
||||
// Use 'data' for GitHub Pages deployment, '../data' for local development
|
||||
this.baseDataPath = './data';
|
||||
}
|
||||
|
||||
// Load all agent names from directory structure
|
||||
async loadAgentList() {
|
||||
try {
|
||||
// Since we can't directly list directories in the browser,
|
||||
// we'll try to load known agents based on common patterns
|
||||
const potentialAgents = [
|
||||
'gemini-2.5-flash',
|
||||
'qwen3-max',
|
||||
'deepseek-chat-v3.1',
|
||||
'gpt-5',
|
||||
'claude-3.7-sonnet',
|
||||
];
|
||||
|
||||
const agents = [];
|
||||
for (const agent of potentialAgents) {
|
||||
try {
|
||||
console.log(`Checking agent: ${agent}`);
|
||||
const response = await fetch(`${this.baseDataPath}/agent_data/${agent}/position/position.jsonl`);
|
||||
if (response.ok) {
|
||||
agents.push(agent);
|
||||
console.log(`Added agent: ${agent}`);
|
||||
} else {
|
||||
console.log(`Agent ${agent} not found (status: ${response.status})`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`Agent ${agent} error:`, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
return agents;
|
||||
} catch (error) {
|
||||
console.error('Error loading agent list:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Load position data for a specific agent
|
||||
async loadAgentPositions(agentName) {
|
||||
try {
|
||||
const response = await fetch(`${this.baseDataPath}/agent_data/${agentName}/position/position.jsonl`);
|
||||
if (!response.ok) throw new Error(`Failed to load positions for ${agentName}`);
|
||||
|
||||
const text = await response.text();
|
||||
const lines = text.trim().split('\n').filter(line => line.trim() !== '');
|
||||
const positions = lines.map(line => {
|
||||
try {
|
||||
return JSON.parse(line);
|
||||
} catch (parseError) {
|
||||
console.error(`Error parsing line for ${agentName}:`, line, parseError);
|
||||
return null;
|
||||
}
|
||||
}).filter(pos => pos !== null);
|
||||
|
||||
console.log(`Loaded ${positions.length} positions for ${agentName}`);
|
||||
return positions;
|
||||
} catch (error) {
|
||||
console.error(`Error loading positions for ${agentName}:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Load price data for a specific stock symbol
|
||||
async loadStockPrice(symbol) {
|
||||
if (this.priceCache[symbol]) {
|
||||
return this.priceCache[symbol];
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.baseDataPath}/daily_prices_${symbol}.json`);
|
||||
if (!response.ok) throw new Error(`Failed to load price for ${symbol}`);
|
||||
|
||||
const data = await response.json();
|
||||
this.priceCache[symbol] = data['Time Series (Daily)'];
|
||||
return this.priceCache[symbol];
|
||||
} catch (error) {
|
||||
console.error(`Error loading price for ${symbol}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Get closing price for a symbol on a specific date
|
||||
async getClosingPrice(symbol, date) {
|
||||
const prices = await this.loadStockPrice(symbol);
|
||||
if (!prices || !prices[date]) {
|
||||
return null;
|
||||
}
|
||||
return parseFloat(prices[date]['4. close']);
|
||||
}
|
||||
|
||||
// Calculate total asset value for a position on a given date
|
||||
async calculateAssetValue(position, date) {
|
||||
let totalValue = position.positions.CASH || 0;
|
||||
|
||||
// Get all stock symbols (exclude CASH)
|
||||
const symbols = Object.keys(position.positions).filter(s => s !== 'CASH');
|
||||
|
||||
for (const symbol of symbols) {
|
||||
const shares = position.positions[symbol];
|
||||
if (shares > 0) {
|
||||
const price = await this.getClosingPrice(symbol, date);
|
||||
if (price) {
|
||||
totalValue += shares * price;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return totalValue;
|
||||
}
|
||||
|
||||
// Load complete data for an agent including asset values over time
|
||||
async loadAgentData(agentName) {
|
||||
console.log(`Starting to load data for ${agentName}...`);
|
||||
const positions = await this.loadAgentPositions(agentName);
|
||||
if (positions.length === 0) {
|
||||
console.log(`No positions found for ${agentName}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(`Processing ${positions.length} positions for ${agentName}...`);
|
||||
|
||||
// Group positions by date and take only the last position for each date
|
||||
const positionsByDate = {};
|
||||
positions.forEach(position => {
|
||||
const date = position.date;
|
||||
if (!positionsByDate[date] || position.id > positionsByDate[date].id) {
|
||||
positionsByDate[date] = position;
|
||||
}
|
||||
});
|
||||
|
||||
// Convert to array and sort by date
|
||||
const uniquePositions = Object.values(positionsByDate).sort((a, b) => {
|
||||
if (a.date !== b.date) {
|
||||
return a.date.localeCompare(b.date);
|
||||
}
|
||||
return a.id - b.id;
|
||||
});
|
||||
|
||||
console.log(`Reduced from ${positions.length} to ${uniquePositions.length} unique daily positions for ${agentName}`);
|
||||
|
||||
const assetHistory = [];
|
||||
|
||||
for (const position of uniquePositions) {
|
||||
const date = position.date;
|
||||
const assetValue = await this.calculateAssetValue(position, date);
|
||||
assetHistory.push({
|
||||
date: date,
|
||||
value: assetValue,
|
||||
id: position.id,
|
||||
action: position.this_action || null
|
||||
});
|
||||
}
|
||||
|
||||
const result = {
|
||||
name: agentName,
|
||||
positions: positions,
|
||||
assetHistory: assetHistory,
|
||||
initialValue: assetHistory[0]?.value || 10000,
|
||||
currentValue: assetHistory[assetHistory.length - 1]?.value || 0,
|
||||
return: assetHistory.length > 0 ?
|
||||
((assetHistory[assetHistory.length - 1].value - assetHistory[0].value) / assetHistory[0].value * 100) : 0
|
||||
};
|
||||
|
||||
console.log(`Successfully loaded data for ${agentName}:`, {
|
||||
positions: positions.length,
|
||||
assetHistory: assetHistory.length,
|
||||
initialValue: result.initialValue,
|
||||
currentValue: result.currentValue,
|
||||
return: result.return
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Load QQQ invesco data
|
||||
async loadQQQData() {
|
||||
try {
|
||||
console.log('Loading QQQ invesco data...');
|
||||
const response = await fetch(`${this.baseDataPath}/Adaily_prices_QQQ.json`);
|
||||
if (!response.ok) throw new Error('Failed to load QQQ data');
|
||||
|
||||
const data = await response.json();
|
||||
const timeSeries = data['Time Series (Daily)'];
|
||||
|
||||
// Convert to asset history format
|
||||
const assetHistory = [];
|
||||
const dates = Object.keys(timeSeries).sort();
|
||||
|
||||
// Calculate QQQ performance starting from first agent's initial value
|
||||
const agentNames = Object.keys(this.agentData);
|
||||
let initialValue = 10000; // Default initial value
|
||||
|
||||
if (agentNames.length > 0) {
|
||||
const firstAgent = this.agentData[agentNames[0]];
|
||||
if (firstAgent && firstAgent.assetHistory.length > 0) {
|
||||
initialValue = firstAgent.assetHistory[0].value;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the earliest start date and latest end date across all agents
|
||||
let startDate = null;
|
||||
let endDate = null;
|
||||
if (agentNames.length > 0) {
|
||||
agentNames.forEach(agentName => {
|
||||
const agent = this.agentData[agentName];
|
||||
if (agent && agent.assetHistory.length > 0) {
|
||||
const agentStartDate = agent.assetHistory[0].date;
|
||||
const agentEndDate = agent.assetHistory[agent.assetHistory.length - 1].date;
|
||||
|
||||
if (!startDate || agentStartDate < startDate) {
|
||||
startDate = agentStartDate;
|
||||
}
|
||||
if (!endDate || agentEndDate > endDate) {
|
||||
endDate = agentEndDate;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let qqqStartPrice = null;
|
||||
let currentValue = initialValue;
|
||||
|
||||
for (const date of dates) {
|
||||
if (startDate && date < startDate) continue;
|
||||
if (endDate && date > endDate) continue;
|
||||
|
||||
const price = parseFloat(timeSeries[date]['4. close']);
|
||||
if (!qqqStartPrice) {
|
||||
qqqStartPrice = price;
|
||||
}
|
||||
|
||||
// Calculate QQQ performance relative to start
|
||||
const qqqReturn = (price - qqqStartPrice) / qqqStartPrice;
|
||||
currentValue = initialValue * (1 + qqqReturn);
|
||||
|
||||
assetHistory.push({
|
||||
date: date,
|
||||
value: currentValue,
|
||||
id: `qqq-${date}`,
|
||||
action: null
|
||||
});
|
||||
}
|
||||
|
||||
const result = {
|
||||
name: 'QQQ',
|
||||
positions: [],
|
||||
assetHistory: assetHistory,
|
||||
initialValue: initialValue,
|
||||
currentValue: assetHistory.length > 0 ? assetHistory[assetHistory.length - 1].value : initialValue,
|
||||
return: assetHistory.length > 0 ?
|
||||
((assetHistory[assetHistory.length - 1].value - assetHistory[0].value) / assetHistory[0].value * 100) : 0
|
||||
};
|
||||
|
||||
console.log('Successfully loaded QQQ data:', {
|
||||
assetHistory: assetHistory.length,
|
||||
initialValue: result.initialValue,
|
||||
currentValue: result.currentValue,
|
||||
return: result.return
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error loading QQQ data:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Load all agents data
|
||||
async loadAllAgentsData() {
|
||||
console.log('Starting to load all agents data...');
|
||||
const agents = await this.loadAgentList();
|
||||
console.log('Found agents:', agents);
|
||||
const allData = {};
|
||||
|
||||
for (const agent of agents) {
|
||||
console.log(`Loading data for ${agent}...`);
|
||||
const data = await this.loadAgentData(agent);
|
||||
if (data) {
|
||||
allData[agent] = data;
|
||||
console.log(`Successfully added ${agent} to allData`);
|
||||
} else {
|
||||
console.log(`Failed to load data for ${agent}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Final allData:', Object.keys(allData));
|
||||
this.agentData = allData;
|
||||
|
||||
// Load QQQ invesco data
|
||||
const qqqData = await this.loadQQQData();
|
||||
if (qqqData) {
|
||||
allData['QQQ'] = qqqData;
|
||||
console.log('Successfully added QQQ invesco to allData');
|
||||
}
|
||||
|
||||
return allData;
|
||||
}
|
||||
|
||||
// Get current holdings for an agent (latest position)
|
||||
getCurrentHoldings(agentName) {
|
||||
const data = this.agentData[agentName];
|
||||
if (!data || !data.positions || data.positions.length === 0) return null;
|
||||
|
||||
const latestPosition = data.positions[data.positions.length - 1];
|
||||
return latestPosition && latestPosition.positions ? latestPosition.positions : null;
|
||||
}
|
||||
|
||||
// Get trade history for an agent
|
||||
getTradeHistory(agentName) {
|
||||
const data = this.agentData[agentName];
|
||||
if (!data) return [];
|
||||
|
||||
return data.positions
|
||||
.filter(p => p.this_action)
|
||||
.map(p => ({
|
||||
date: p.date,
|
||||
action: p.this_action.action,
|
||||
symbol: p.this_action.symbol,
|
||||
amount: p.this_action.amount
|
||||
}))
|
||||
.reverse(); // Most recent first
|
||||
}
|
||||
|
||||
// Format number as currency
|
||||
formatCurrency(value) {
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
minimumFractionDigits: 2
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
// Format percentage
|
||||
formatPercent(value) {
|
||||
const sign = value >= 0 ? '+' : '';
|
||||
return `${sign}${value.toFixed(2)}%`;
|
||||
}
|
||||
|
||||
// Get nice display name for agent
|
||||
getAgentDisplayName(agentName) {
|
||||
const names = {
|
||||
'gemini-2.5-flash': 'Gemini-2.5-flash',
|
||||
'qwen3-max': 'Qwen3-max',
|
||||
'gpt-5': 'GPT-5',
|
||||
'deepseek-chat-v3.1': 'DeepSeek-v3.1',
|
||||
'claude-3.7-sonnet': 'Claude 3.7 Sonnet',
|
||||
'QQQ': 'QQQ invesco'
|
||||
};
|
||||
return names[agentName] || agentName;
|
||||
}
|
||||
|
||||
// Get icon for agent (emoji/symbol representation)
|
||||
getAgentIcon(agentName) {
|
||||
const icons = {
|
||||
'gemini-2.5-flash': '🤖',
|
||||
'qwen3-max': '🧠',
|
||||
'gpt-5': '🔮',
|
||||
'claude-3.7-sonnet': '🎭',
|
||||
'deepseek-chat-v3.1': '🔬',
|
||||
'QQQ': '📈'
|
||||
};
|
||||
return icons[agentName] || '🤖';
|
||||
}
|
||||
|
||||
// Get brand color for agent
|
||||
getAgentBrandColor(agentName) {
|
||||
const colors = {
|
||||
'gemini-2.5-flash': '#8A2BE2', // Google purple
|
||||
'qwen3-max': '#0066ff', // Qwen Blue
|
||||
'gpt-5': '#10a37f', // OpenAI Green
|
||||
'deepseek-chat-v3.1': '#4a90e2', // DeepSeek Blue
|
||||
'claude-3.7-sonnet': '#cc785c', // Anthropic Orange
|
||||
'QQQ': '#ff6b00' // QQQ Orange
|
||||
};
|
||||
return colors[agentName] || null;
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use in other modules
|
||||
window.DataLoader = DataLoader;
|
||||
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);
|
||||
86
docs/index.html
Normal file
86
docs/index.html
Normal file
@@ -0,0 +1,86 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI-Trader - AI Trading Agent Benchmark</title>
|
||||
<link rel="stylesheet" href="assets/css/styles.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar">
|
||||
<div class="nav-container">
|
||||
<div class="nav-brand">
|
||||
<span class="brand-icon">📊</span>
|
||||
<h1>AI-Trader</h1>
|
||||
</div>
|
||||
<ul class="nav-menu">
|
||||
<li><a href="index.html" class="nav-link active">Asset Evolution</a></li>
|
||||
<li><a href="portfolio.html" class="nav-link">Portfolio Analysis</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container">
|
||||
<header class="page-header">
|
||||
<h2>AI Agent Performance Comparison</h2>
|
||||
<p class="subtitle">Track how different AI models perform in Nasdaq-100 stock trading</p>
|
||||
</header>
|
||||
|
||||
<section class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Active Agents</div>
|
||||
<div class="stat-value" id="agent-count">-</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Trading Period</div>
|
||||
<div class="stat-value" id="trading-period">-</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Best Performer</div>
|
||||
<div class="stat-value" id="best-performer">-</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Best Return</div>
|
||||
<div class="stat-value" id="avg-return">-</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="chart-section">
|
||||
<div class="chart-card hero">
|
||||
<div class="chart-header">
|
||||
<h3>Total Asset Value Over Time</h3>
|
||||
<div class="chart-controls">
|
||||
<button class="btn-secondary" id="toggle-log">Linear Scale</button>
|
||||
<button class="btn-secondary" id="export-chart">Export Data</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<canvas id="assetChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="legend-section">
|
||||
<h3>Agent Legend</h3>
|
||||
<div class="agent-legend" id="agentLegend"></div>
|
||||
</section>
|
||||
|
||||
<div class="loading-overlay" id="loadingOverlay">
|
||||
<div class="spinner"></div>
|
||||
<p>Loading trading data...</p>
|
||||
</div>
|
||||
|
||||
<button class="scroll-to-top" id="scrollToTop" aria-label="Scroll to top">
|
||||
↑
|
||||
</button>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<p>AI-Trader © 2025 | AI-Powered Trading Agent Benchmark</p>
|
||||
</footer>
|
||||
|
||||
<script src="assets/js/data-loader.js"></script>
|
||||
<script src="assets/js/asset-chart.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
112
docs/portfolio.html
Normal file
112
docs/portfolio.html
Normal file
@@ -0,0 +1,112 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Portfolio Analysis - LiveTradeBench</title>
|
||||
<link rel="stylesheet" href="assets/css/styles.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar">
|
||||
<div class="nav-container">
|
||||
<div class="nav-brand">
|
||||
<span class="brand-icon">📊</span>
|
||||
<h1>LiveTradeBench</h1>
|
||||
</div>
|
||||
<ul class="nav-menu">
|
||||
<li><a href="index.html" class="nav-link">Asset Evolution</a></li>
|
||||
<li><a href="portfolio.html" class="nav-link active">Portfolio Analysis</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container">
|
||||
<header class="page-header">
|
||||
<h2>Agent Portfolio Breakdown</h2>
|
||||
<p class="subtitle">Detailed analysis of each AI agent's holdings and performance</p>
|
||||
</header>
|
||||
|
||||
<section class="agent-selector-section">
|
||||
<label for="agentSelect">Select Agent:</label>
|
||||
<select id="agentSelect" class="agent-select"></select>
|
||||
</section>
|
||||
|
||||
<section class="portfolio-grid">
|
||||
<div class="portfolio-card">
|
||||
<h3>Performance Metrics</h3>
|
||||
<div class="metrics-list">
|
||||
<div class="metric-item">
|
||||
<span class="metric-label">Total Asset Value</span>
|
||||
<span class="metric-value" id="totalAsset">$0.00</span>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-label">Total Return</span>
|
||||
<span class="metric-value" id="totalReturn">0.00%</span>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-label">Cash Position</span>
|
||||
<span class="metric-value" id="cashPosition">$0.00</span>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-label">Total Trades</span>
|
||||
<span class="metric-value" id="totalTrades">0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="portfolio-card">
|
||||
<h3>Asset Allocation</h3>
|
||||
<div class="chart-container-small">
|
||||
<canvas id="allocationChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="holdings-section">
|
||||
<h3>Current Holdings</h3>
|
||||
<div class="holdings-scroll-wrapper">
|
||||
<div class="holdings-table-container">
|
||||
<table class="holdings-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Symbol</th>
|
||||
<th>Shares</th>
|
||||
<th>Current Price</th>
|
||||
<th>Market Value</th>
|
||||
<th>Weight</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="holdingsTableBody">
|
||||
<!-- Populated by JavaScript -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="trade-history-section">
|
||||
<h3>Recent Trade Activity</h3>
|
||||
<div class="trade-timeline" id="tradeTimeline">
|
||||
<!-- Populated by JavaScript -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="loading-overlay" id="loadingOverlay">
|
||||
<div class="spinner"></div>
|
||||
<p>Loading portfolio data...</p>
|
||||
</div>
|
||||
|
||||
<button class="scroll-to-top" id="scrollToTop" aria-label="Scroll to top">
|
||||
↑
|
||||
</button>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<p>LiveTradeBench © 2025 | AI-Powered Trading Agent Benchmark</p>
|
||||
</footer>
|
||||
|
||||
<script src="assets/js/data-loader.js"></script>
|
||||
<script src="assets/js/portfolio.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user