/home/devscfvi/app.devsquantum.com/js/analytics.js
// ============================================
// ADVANCED ANALYTICS MODULE - COMPLETE
// ============================================
// Render Analytics Dashboard
function renderAnalytics() {
if (!state.analytics) {
return `
<div class="min-h-screen p-4">
<div class="max-w-2xl mx-auto">
<div class="mb-6 pt-4">
<h1 class="text-2xl font-bold text-white">Analytics</h1>
<p class="text-violet-300">Loading insights...</p>
</div>
<div class="flex items-center justify-center py-12">
<div class="spinner"></div>
</div>
</div>
</div>
`;
}
const analytics = state.analytics;
const hasData = state.trades && state.trades.length > 0;
return `
<div class="min-h-screen p-4">
<div class="max-w-4xl mx-auto">
<!-- Header -->
<div class="mb-6 pt-4">
<h1 class="text-2xl font-bold text-white mb-1">Advanced Analytics</h1>
<p class="text-sm text-violet-300">Deep insights into your trading performance</p>
</div>
${!hasData ? renderEmptyAnalytics() : `
<!-- Tab Navigation -->
<div class="glass rounded-2xl p-2 mb-6 border border-violet-500/20">
<div class="grid grid-cols-4 gap-2">
<button onclick="switchAnalyticsTab('overview')"
id="tab-overview"
class="analytics-tab py-3 rounded-xl font-semibold transition text-sm">
Overview
</button>
<button onclick="switchAnalyticsTab('category')"
id="tab-category"
class="analytics-tab py-3 rounded-xl font-semibold transition text-sm">
Strategy
</button>
<button onclick="switchAnalyticsTab('psychology')"
id="tab-psychology"
class="analytics-tab py-3 rounded-xl font-semibold transition text-sm">
Psychology
</button>
<button onclick="switchAnalyticsTab('timing')"
id="tab-timing"
class="analytics-tab py-3 rounded-xl font-semibold transition text-sm">
Timing
</button>
</div>
</div>
<!-- Tab Content -->
<div id="analytics-content"></div>
`}
</div>
</div>
`;
}
// Empty state
function renderEmptyAnalytics() {
return `
<div class="glass rounded-3xl p-12 text-center border border-violet-500/20">
<svg class="w-16 h-16 mx-auto text-violet-400/50 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
</svg>
<p class="text-lg text-white mb-2">No Data Yet</p>
<p class="text-sm text-slate-400 mb-4">Start tracking trades to see analytics</p>
<button onclick="navigateTo('calculator')"
class="gradient-bg text-white px-6 py-3 rounded-xl font-semibold active:scale-95 transition">
Create First Trade
</button>
</div>
`;
}
// Switch between analytics tabs
let currentAnalyticsTab = 'overview';
function switchAnalyticsTab(tab) {
currentAnalyticsTab = tab;
// Update tab buttons
document.querySelectorAll('.analytics-tab').forEach(btn => {
const btnTab = btn.id.replace('tab-', '');
if (btnTab === tab) {
btn.classList.add('bg-violet-600', 'text-white');
btn.classList.remove('bg-slate-900/50', 'text-slate-400');
} else {
btn.classList.remove('bg-violet-600', 'text-white');
btn.classList.add('bg-slate-900/50', 'text-slate-400');
}
});
// Render content
const container = document.getElementById('analytics-content');
if (!container) return;
switch(tab) {
case 'overview':
container.innerHTML = renderOverviewTab();
setTimeout(() => initOverviewCharts(), 100);
break;
case 'category':
container.innerHTML = renderCategoryTab();
setTimeout(() => initCategoryCharts(), 100);
break;
case 'psychology':
container.innerHTML = renderPsychologyTab();
setTimeout(() => initPsychologyCharts(), 100);
break;
case 'timing':
container.innerHTML = renderTimingTab();
setTimeout(() => initTimingCharts(), 100);
break;
}
}
// ============================================
// OVERVIEW TAB
// ============================================
function renderOverviewTab() {
const analytics = state.analytics;
const streaks = analytics.streaks;
const stats = state.stats || {};
return `
<div class="space-y-4">
<!-- Quick Stats Card -->
<div class="glass rounded-2xl p-4 border border-violet-500/20">
<div class="flex items-center justify-between mb-3">
<h3 class="text-sm font-semibold text-violet-300">Quick Stats</h3>
<button onclick="navigateTo('stats')"
class="text-xs text-violet-400 hover:text-violet-300 transition flex items-center gap-1">
View Full Stats
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</button>
</div>
<div class="grid grid-cols-4 gap-3">
<div class="text-center">
<p class="text-2xl font-bold text-white">${stats.total_trades || 0}</p>
<p class="text-xs text-slate-400">Trades</p>
</div>
<div class="text-center">
<p class="text-2xl font-bold ${parseFloat(stats.win_rate || 0) >= 50 ? 'text-green-400' : 'text-red-400'}">
${stats.win_rate || 0}%
</p>
<p class="text-xs text-slate-400">Win Rate</p>
</div>
<div class="text-center">
<p class="text-2xl font-bold ${parseFloat(stats.net_pnl || 0) >= 0 ? 'text-green-400' : 'text-red-400'}">
${parseFloat(stats.net_pnl || 0) >= 0 ? '+' : ''}${formatCurrency(stats.net_pnl || 0)}
</p>
<p class="text-xs text-slate-400">Net P&L</p>
</div>
<div class="text-center">
<p class="text-2xl font-bold text-violet-400">1:${parseFloat(stats.avg_rr || 0).toFixed(2)}</p>
<p class="text-xs text-slate-400">Avg R:R</p>
</div>
</div>
</div>
<!-- Streaks -->
<div class="glass rounded-3xl p-6 border border-violet-500/20">
<h3 class="text-lg font-semibold text-white mb-4">Current Streaks</h3>
<div class="grid grid-cols-2 gap-4">
<div class="bg-green-500/10 rounded-2xl p-4 border border-green-500/20">
<p class="text-xs text-green-300 mb-1">Current Win Streak</p>
<p class="text-4xl font-bold text-green-400">${streaks.current_win_streak || 0}</p>
<p class="text-xs text-slate-400 mt-2">Best: ${streaks.max_win_streak || 0}</p>
</div>
<div class="bg-red-500/10 rounded-2xl p-4 border border-red-500/20">
<p class="text-xs text-red-300 mb-1">Current Loss Streak</p>
<p class="text-4xl font-bold text-red-400">${streaks.current_loss_streak || 0}</p>
<p class="text-xs text-slate-400 mt-2">Worst: ${streaks.max_loss_streak || 0}</p>
</div>
</div>
</div>
<!-- Top Performing Coins -->
<div class="glass rounded-3xl p-6 border border-violet-500/20">
<h3 class="text-lg font-semibold text-white mb-4">Performance by Coin</h3>
${analytics.byCoin && analytics.byCoin.length > 0 ? `
<div class="space-y-3">
${analytics.byCoin.slice(0, 5).map(coin => `
<div class="bg-slate-900/50 rounded-xl p-4 border border-violet-500/20">
<div class="flex items-center justify-between mb-2">
<span class="text-lg font-bold text-white">${coin.coin}</span>
<span class="text-sm font-semibold ${parseFloat(coin.net_pnl) >= 0 ? 'text-green-400' : 'text-red-400'}">
${parseFloat(coin.net_pnl) >= 0 ? '+' : ''}${formatCurrency(coin.net_pnl)}
</span>
</div>
<div class="grid grid-cols-3 gap-2 text-xs">
<div>
<p class="text-slate-400">Trades</p>
<p class="text-white font-semibold">${coin.total_trades}</p>
</div>
<div>
<p class="text-slate-400">Win Rate</p>
<p class="text-white font-semibold">${coin.win_rate || 0}%</p>
</div>
<div>
<p class="text-slate-400">Avg R:R</p>
<p class="text-white font-semibold">1:${parseFloat(coin.avg_rr || 0).toFixed(2)}</p>
</div>
</div>
</div>
`).join('')}
</div>
` : '<p class="text-slate-400 text-center py-4">No data available</p>'}
</div>
<!-- Performance Chart -->
${analytics.byCoin && analytics.byCoin.length > 0 ? `
<div class="glass rounded-3xl p-6 border border-violet-500/20">
<h3 class="text-lg font-semibold text-white mb-4">Win Rate by Coin</h3>
<div style="height: 300px;">
<canvas id="coinWinRateChart"></canvas>
</div>
</div>
` : ''}
</div>
`;
}
function initOverviewCharts() {
const analytics = state.analytics;
if (!analytics.byCoin || analytics.byCoin.length === 0) return;
const canvas = document.getElementById('coinWinRateChart');
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (window.coinWinRateChart && typeof window.coinWinRateChart.destroy === 'function') {
window.coinWinRateChart.destroy();
}
const topCoins = analytics.byCoin.slice(0, 10);
window.coinWinRateChart = new Chart(ctx, {
type: 'bar',
data: {
labels: topCoins.map(c => c.coin),
datasets: [{
label: 'Win Rate %',
data: topCoins.map(c => parseFloat(c.win_rate || 0)),
backgroundColor: topCoins.map(c => parseFloat(c.win_rate || 0) >= 50 ? '#10b981' : '#ef4444'),
borderRadius: 8
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
padding: 12,
callbacks: {
label: (context) => context.parsed.y.toFixed(1) + '%'
}
}
},
scales: {
y: {
beginAtZero: true,
max: 100,
grid: { color: 'rgba(139, 92, 246, 0.1)' },
ticks: {
color: '#a78bfa',
callback: (value) => value + '%'
}
},
x: {
grid: { display: false },
ticks: { color: '#a78bfa' }
}
}
}
});
}
// ============================================
// CATEGORY TAB
// ============================================
function renderCategoryTab() {
const analytics = state.analytics;
return `
<div class="space-y-4">
<!-- Category Performance -->
<div class="glass rounded-3xl p-6 border border-violet-500/20">
<h3 class="text-lg font-semibold text-white mb-4">Strategy Performance</h3>
${analytics.byCategory && analytics.byCategory.length > 0 ? `
<div class="space-y-3">
${analytics.byCategory.map(cat => `
<div class="bg-slate-900/50 rounded-xl p-4 border border-violet-500/20">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<span class="category-badge category-${cat.trade_category}">
${getCategoryLabel(cat.trade_category)}
</span>
<span class="text-sm text-slate-400">${cat.total_trades} trades</span>
</div>
<span class="text-lg font-bold ${parseFloat(cat.net_pnl) >= 0 ? 'text-green-400' : 'text-red-400'}">
${parseFloat(cat.net_pnl) >= 0 ? '+' : ''}${formatCurrency(cat.net_pnl)}
</span>
</div>
<div class="grid grid-cols-3 gap-3 text-sm">
<div>
<p class="text-slate-400 text-xs">Win Rate</p>
<p class="text-white font-semibold">${cat.win_rate || 0}%</p>
</div>
<div>
<p class="text-slate-400 text-xs">W/L</p>
<p class="text-white font-semibold">${cat.wins}/${cat.losses}</p>
</div>
<div>
<p class="text-slate-400 text-xs">Avg R:R</p>
<p class="text-white font-semibold">1:${parseFloat(cat.avg_rr || 0).toFixed(2)}</p>
</div>
</div>
</div>
`).join('')}
</div>
` : '<p class="text-slate-400 text-center py-8">No category data yet. Start adding categories to your trades!</p>'}
</div>
${analytics.byCategory && analytics.byCategory.length > 0 ? `
<!-- Category Charts -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="glass rounded-3xl p-6 border border-violet-500/20">
<h3 class="text-lg font-semibold text-white mb-4">P&L by Strategy</h3>
<div style="height: 250px;">
<canvas id="categoryPnlChart"></canvas>
</div>
</div>
<div class="glass rounded-3xl p-6 border border-violet-500/20">
<h3 class="text-lg font-semibold text-white mb-4">Trade Distribution</h3>
<div style="height: 250px;">
<canvas id="categoryDistChart"></canvas>
</div>
</div>
</div>
<!-- Best/Worst Strategy -->
<div class="grid grid-cols-2 gap-4">
${(() => {
const sorted = [...analytics.byCategory].sort((a, b) => parseFloat(b.net_pnl) - parseFloat(a.net_pnl));
const best = sorted[0];
const worst = sorted[sorted.length - 1];
return `
<div class="glass rounded-2xl p-5 border border-green-500/20">
<p class="text-xs text-green-300 mb-2">🏆 Best Strategy</p>
<p class="text-xl font-bold text-white">${getCategoryLabel(best.trade_category)}</p>
<p class="text-2xl font-bold text-green-400 mt-2">+${formatCurrency(best.net_pnl)}</p>
<p class="text-xs text-slate-400 mt-1">${best.win_rate}% win rate</p>
</div>
<div class="glass rounded-2xl p-5 border border-red-500/20">
<p class="text-xs text-red-300 mb-2">⚠️ Needs Work</p>
<p class="text-xl font-bold text-white">${getCategoryLabel(worst.trade_category)}</p>
<p class="text-2xl font-bold text-red-400 mt-2">${formatCurrency(worst.net_pnl)}</p>
<p class="text-xs text-slate-400 mt-1">${worst.win_rate}% win rate</p>
</div>
`;
})()}
</div>
` : ''}
</div>
`;
}
function initCategoryCharts() {
const analytics = state.analytics;
if (!analytics.byCategory || analytics.byCategory.length === 0) return;
// P&L Chart
const pnlCanvas = document.getElementById('categoryPnlChart');
if (pnlCanvas) {
const ctx = pnlCanvas.getContext('2d');
if (window.categoryPnlChart && typeof window.categoryPnlChart.destroy === 'function') {
window.categoryPnlChart.destroy();
}
window.categoryPnlChart = new Chart(ctx, {
type: 'bar',
data: {
labels: analytics.byCategory.map(c => getCategoryLabel(c.trade_category)),
datasets: [{
label: 'Net P&L',
data: analytics.byCategory.map(c => parseFloat(c.net_pnl)),
backgroundColor: analytics.byCategory.map(c => parseFloat(c.net_pnl) >= 0 ? '#10b981' : '#ef4444'),
borderRadius: 8
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
padding: 12,
callbacks: {
label: (context) => '$' + context.parsed.y.toFixed(2)
}
}
},
scales: {
y: {
grid: { color: 'rgba(139, 92, 246, 0.1)' },
ticks: {
color: '#a78bfa',
callback: (value) => '$' + value.toFixed(0)
}
},
x: {
grid: { display: false },
ticks: { color: '#a78bfa', font: { size: 10 } }
}
}
}
});
}
// Distribution Chart
const distCanvas = document.getElementById('categoryDistChart');
if (distCanvas) {
const ctx = distCanvas.getContext('2d');
if (window.categoryDistChart && typeof window.categoryDistChart.destroy === 'function') {
window.categoryDistChart.destroy();
}
window.categoryDistChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: analytics.byCategory.map(c => getCategoryLabel(c.trade_category)),
datasets: [{
data: analytics.byCategory.map(c => c.total_trades),
backgroundColor: ['#8b5cf6', '#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#ec4899']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: { color: '#a78bfa', font: { size: 10 }, padding: 10 }
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
padding: 12
}
}
}
});
}
}
// ============================================
// PSYCHOLOGY TAB
// ============================================
function renderPsychologyTab() {
const analytics = state.analytics;
return `
<div class="space-y-4">
<!-- Emotion Performance -->
<div class="glass rounded-3xl p-6 border border-violet-500/20">
<h3 class="text-lg font-semibold text-white mb-4">Performance by Emotion</h3>
${analytics.byEmotion && analytics.byEmotion.length > 0 ? `
<div class="space-y-3">
${analytics.byEmotion.map(emo => `
<div class="bg-slate-900/50 rounded-xl p-4 border border-violet-500/20">
<div class="flex items-center justify-between mb-2">
<span class="emotion-badge emotion-${emo.emotion}">
${getEmotionLabel(emo.emotion)}
</span>
<div class="text-right">
<p class="text-sm text-slate-400">${emo.count} trades</p>
<p class="text-lg font-bold ${parseFloat(emo.win_rate) >= 50 ? 'text-green-400' : 'text-red-400'}">
${emo.win_rate}% wins
</p>
</div>
</div>
<div class="w-full bg-slate-800 rounded-full h-2 mt-2">
<div class="h-2 rounded-full ${parseFloat(emo.win_rate) >= 50 ? 'bg-green-500' : 'bg-red-500'}"
style="width: ${emo.win_rate}%"></div>
</div>
</div>
`).join('')}
</div>
` : '<p class="text-slate-400 text-center py-8">No emotion data yet. Start tracking your emotions!</p>'}
</div>
${analytics.byEmotion && analytics.byEmotion.length > 0 ? `
<!-- Emotion Chart -->
<div class="glass rounded-3xl p-6 border border-violet-500/20">
<h3 class="text-lg font-semibold text-white mb-4">Win Rate by Emotion</h3>
<div style="height: 300px;">
<canvas id="emotionChart"></canvas>
</div>
</div>
<!-- Key Insights -->
<div class="glass rounded-3xl p-6 border border-violet-500/20">
<h3 class="text-lg font-semibold text-white mb-4">💡 Key Insights</h3>
${(() => {
const sorted = [...analytics.byEmotion].sort((a, b) => parseFloat(b.win_rate) - parseFloat(a.win_rate));
const best = sorted[0];
const worst = sorted[sorted.length - 1];
return `
<div class="space-y-4">
<div class="bg-green-500/10 rounded-xl p-4 border border-green-500/20">
<p class="text-sm text-green-300 mb-2">✅ Best Trading Emotion</p>
<p class="text-lg font-bold text-white">${getEmotionLabel(best.emotion)}</p>
<p class="text-sm text-slate-400 mt-1">
${best.win_rate}% win rate across ${best.count} trades
</p>
<p class="text-xs text-green-300 mt-2">
💡 Try to achieve this emotional state more often
</p>
</div>
<div class="bg-red-500/10 rounded-xl p-4 border border-red-500/20">
<p class="text-sm text-red-300 mb-2">⚠️ Problematic Emotion</p>
<p class="text-lg font-bold text-white">${getEmotionLabel(worst.emotion)}</p>
<p class="text-sm text-slate-400 mt-1">
${worst.win_rate}% win rate across ${worst.count} trades
</p>
<p class="text-xs text-red-300 mt-2">
💡 Avoid trading when feeling ${worst.emotion} - take a break
</p>
</div>
</div>
`;
})()}
</div>
` : ''}
</div>
`;
}
function initPsychologyCharts() {
const analytics = state.analytics;
if (!analytics.byEmotion || analytics.byEmotion.length === 0) return;
const canvas = document.getElementById('emotionChart');
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (window.emotionChart && typeof window.emotionChart.destroy === 'function') {
window.emotionChart.destroy();
}
window.emotionChart = new Chart(ctx, {
type: 'radar',
data: {
labels: analytics.byEmotion.map(e => getEmotionLabel(e.emotion)),
datasets: [{
label: 'Win Rate %',
data: analytics.byEmotion.map(e => parseFloat(e.win_rate || 0)),
backgroundColor: 'rgba(139, 92, 246, 0.2)',
borderColor: '#8b5cf6',
pointBackgroundColor: '#8b5cf6',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: '#8b5cf6'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
padding: 12,
callbacks: {
label: (context) => context.parsed.r.toFixed(1) + '% win rate'
}
}
},
scales: {
r: {
beginAtZero: true,
max: 100,
ticks: {
color: '#a78bfa',
backdropColor: 'transparent',
callback: (value) => value + '%'
},
grid: { color: 'rgba(139, 92, 246, 0.2)' },
pointLabels: { color: '#a78bfa', font: { size: 11 } }
}
}
}
});
}
// ============================================
// TIMING TAB
// ============================================
function renderTimingTab() {
const analytics = state.analytics;
return `
<div class="space-y-4">
<!-- Trading Hours -->
<div class="glass rounded-3xl p-6 border border-violet-500/20">
<h3 class="text-lg font-semibold text-white mb-4">Performance by Hour</h3>
${analytics.byHour && analytics.byHour.length > 0 ? `
<div class="space-y-2">
${analytics.byHour.map(hour => `
<div class="flex items-center gap-3">
<span class="text-sm text-slate-400 w-16">
${String(hour.hour).padStart(2, '0')}:00
</span>
<div class="flex-1 bg-slate-900/50 rounded-lg p-3 border border-violet-500/20">
<div class="flex items-center justify-between mb-1">
<span class="text-xs text-slate-400">${hour.trades} trades</span>
<span class="text-sm font-semibold ${parseFloat(hour.win_rate || 0) >= 50 ? 'text-green-400' : 'text-red-400'}">
${hour.win_rate || 0}% wins
</span>
</div>
<div class="w-full bg-slate-800 rounded-full h-1.5">
<div class="h-1.5 rounded-full ${parseFloat(hour.win_rate || 0) >= 50 ? 'bg-green-500' : 'bg-red-500'}"
style="width: ${hour.win_rate || 0}%"></div>
</div>
</div>
</div>
`).join('')}
</div>
` : '<p class="text-slate-400 text-center py-8">Not enough data yet</p>'}
</div>
${analytics.byHour && analytics.byHour.length > 0 ? `
<!-- Trading Hours Chart -->
<div class="glass rounded-3xl p-6 border border-violet-500/20">
<h3 class="text-lg font-semibold text-white mb-4">Win Rate Throughout the Day</h3>
<div style="height: 250px;">
<canvas id="hourChart"></canvas>
</div>
</div>
<!-- Best/Worst Times -->
${(() => {
const sorted = [...analytics.byHour].sort((a, b) => parseFloat(b.win_rate || 0) - parseFloat(a.win_rate || 0));
const best = sorted[0];
const worst = sorted[sorted.length - 1];
return `
<div class="grid grid-cols-2 gap-4">
<div class="glass rounded-2xl p-5 border border-green-500/20">
<p class="text-xs text-green-300 mb-2">🌟 Best Time</p>
<p class="text-3xl font-bold text-white">${String(best.hour).padStart(2, '0')}:00</p>
<p class="text-sm text-green-400 mt-2">${best.win_rate}% win rate</p>
<p class="text-xs text-slate-400 mt-1">${best.trades} trades</p>
</div>
<div class="glass rounded-2xl p-5 border border-red-500/20">
<p class="text-xs text-red-300 mb-2">⚠️ Worst Time</p>
<p class="text-3xl font-bold text-white">${String(worst.hour).padStart(2, '0')}:00</p>
<p class="text-sm text-red-400 mt-2">${worst.win_rate}% win rate</p>
<p class="text-xs text-slate-400 mt-1">${worst.trades} trades</p>
</div>
</div>
`;
})()}
<!-- Recommendations -->
<div class="glass rounded-3xl p-6 border border-blue-500/20">
<h3 class="text-lg font-semibold text-white mb-3">📊 Recommendations</h3>
<div class="space-y-2 text-sm">
${(() => {
const sorted = [...analytics.byHour].sort((a, b) => parseFloat(b.win_rate || 0) - parseFloat(a.win_rate || 0));
const goodHours = sorted.slice(0, 3).map(h => String(h.hour).padStart(2, '0') + ':00').join(', ');
const badHours = sorted.slice(-3).reverse().map(h => String(h.hour).padStart(2, '0') + ':00').join(', ');
return `
<p class="text-green-400">✅ Focus on trading during: ${goodHours}</p>
<p class="text-red-400">⚠️ Be cautious during: ${badHours}</p>
<p class="text-slate-400 mt-3">💡 Consider your energy levels and market conditions at different times</p>
`;
})()}
</div>
</div>
` : ''}
</div>
`;
}
function initTimingCharts() {
const analytics = state.analytics;
if (!analytics.byHour || analytics.byHour.length === 0) return;
const canvas = document.getElementById('hourChart');
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (window.hourChart && typeof window.hourChart.destroy === 'function') {
window.hourChart.destroy();
}
// Sort by hour
const sorted = [...analytics.byHour].sort((a, b) => a.hour - b.hour);
window.hourChart = new Chart(ctx, {
type: 'line',
data: {
labels: sorted.map(h => String(h.hour).padStart(2, '0') + ':00'),
datasets: [{
label: 'Win Rate %',
data: sorted.map(h => parseFloat(h.win_rate || 0)),
borderColor: '#8b5cf6',
backgroundColor: 'rgba(139, 92, 246, 0.1)',
tension: 0.4,
fill: true,
pointBackgroundColor: '#8b5cf6',
pointBorderColor: '#fff',
pointBorderWidth: 2,
pointRadius: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
padding: 12,
callbacks: {
label: (context) => context.parsed.y.toFixed(1) + '% win rate'
}
}
},
scales: {
y: {
beginAtZero: true,
max: 100,
grid: { color: 'rgba(139, 92, 246, 0.1)' },
ticks: {
color: '#a78bfa',
callback: (value) => value + '%'
}
},
x: {
grid: { display: false },
ticks: { color: '#a78bfa', font: { size: 10 } }
}
}
}
});
}
// Initialize Analytics Charts
function initAnalyticsCharts() {
// Set default tab on load
switchAnalyticsTab(currentAnalyticsTab || 'overview');
}
// Make functions global
window.renderAnalytics = renderAnalytics;
window.initAnalyticsCharts = initAnalyticsCharts;
window.switchAnalyticsTab = switchAnalyticsTab;