mirror of
https://github.com/Xe138/AI-Trader.git
synced 2026-04-09 04:07:24 -04:00
init update
This commit is contained in:
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);
|
||||
Reference in New Issue
Block a user