// Trading Bot Dashboard - Client-side JavaScript
// Fetch and update all dashboard data
async function refreshData() {
try {
const response = await fetch('/api/status');
const result = await response.json();
if (result.success) {
updateAccountValue(result.data.account);
updateBotStatus(result.data.bot);
updateTradeSummary(result.data.trades);
updatePositions(result.data.positions);
updateCronJobs(result.data.cron);
updateSystemHealth(result.data.system);
updateLogs(result.data.logs);
updateTimestamp(result.data.timestamp);
} else {
console.error('API error:', result.error);
}
} catch (error) {
console.error('Fetch error:', error);
}
}
// Update account value widget
function updateAccountValue(account) {
const content = document.getElementById('account-content');
if (!account) {
content.innerHTML = `<div class="error">No account data</div>`;
return;
}
const valueClass = account.current_value >= account.initial_portfolio ? 'text-green' : 'text-red';
const pnlClass = account.unrealized_pnl >= 0 ? 'text-green' : 'text-red';
const pnlSign = account.unrealized_pnl >= 0 ? '+' : '';
content.innerHTML = `
<div class="account-summary">
<div class="account-row">
<span class="account-label">Mode:</span>
<span class="account-value badge">${account.mode}</span>
</div>
<div class="account-row">
<span class="account-label">Initial:</span>
<span class="account-value">$${account.initial_portfolio.toLocaleString()}</span>
</div>
<div class="account-row account-main">
<span class="account-label">Current Value:</span>
<span class="account-value large ${valueClass}">$${account.current_value.toLocaleString()}</span>
</div>
<div class="account-row">
<span class="account-label">Unrealized PnL:</span>
<span class="account-value ${pnlClass}">${pnlSign}$${account.unrealized_pnl.toFixed(2)}</span>
</div>
</div>
`;
}
// Update bot status widget
function updateBotStatus(bot) {
const indicator = document.getElementById('bot-status-indicator');
const content = document.getElementById('bot-status-content');
if (bot.running) {
indicator.className = 'status-indicator running';
indicator.textContent = '●';
content.innerHTML = `
<div class="bot-row">
<span class="bot-label">Status:</span>
<span class="bot-value text-green">Running</span>
</div>
<div class="bot-row">
<span class="bot-label">PID:</span>
<span class="bot-value">${bot.pid}</span>
</div>
<div class="bot-row">
<span class="bot-label">Mode:</span>
<span class="bot-value">${bot.mode}</span>
</div>
<div class="bot-row">
<span class="bot-label">Started:</span>
<span class="bot-value text-muted">${bot.uptime}</span>
</div>
`;
} else {
indicator.className = 'status-indicator stopped';
indicator.textContent = '○';
content.innerHTML = `
<div class="bot-row">
<span class="bot-label">Status:</span>
<span class="bot-value text-red">Stopped</span>
</div>
<div class="bot-row">
<span class="bot-label">Mode:</span>
<span class="bot-value">${bot.mode}</span>
</div>
`;
}
}
// Update trade summary widget
function updateTradeSummary(trades) {
const content = document.getElementById('trade-content');
if (trades.error) {
content.innerHTML = `<div class="error">${trades.error}</div>`;
return;
}
const winRateClass = trades.win_rate >= 50 ? 'positive' : 'negative';
const pnlClass = trades.total_pnl >= 0 ? 'positive' : 'negative';
const pnlSign = trades.total_pnl >= 0 ? '+' : '';
content.innerHTML = `
<div class="trade-stats">
<div class="stat-item">
<div class="stat-label">Total Signals</div>
<div class="stat-value">${trades.total_signals || 0}</div>
</div>
<div class="stat-item">
<div class="stat-label">Total Trades</div>
<div class="stat-value">${trades.total_trades || 0}</div>
</div>
<div class="stat-item">
<div class="stat-label">Win Rate</div>
<div class="stat-value ${winRateClass}">${trades.win_rate.toFixed(1)}%</div>
</div>
<div class="stat-item">
<div class="stat-label">Total PnL</div>
<div class="stat-value ${pnlClass}">${pnlSign}$${trades.total_pnl.toFixed(2)}</div>
</div>
<div class="stat-item">
<div class="stat-label">Winning</div>
<div class="stat-value text-green">${trades.winning_trades || 0}</div>
</div>
<div class="stat-item">
<div class="stat-label">Losing</div>
<div class="stat-value text-red">${trades.losing_trades || 0}</div>
</div>
</div>
`;
}
// Update positions held widget
function updatePositions(positions) {
const content = document.getElementById('positions-content');
if (positions.error) {
content.innerHTML = `<div class="error">${positions.error}</div>`;
return;
}
if (positions.positions.length === 0) {
content.innerHTML = `<div class="text-muted">No positions held</div>`;
return;
}
const totalValueClass = positions.total_value >= positions.initial_portfolio ? 'text-red' : 'text-green';
const portfolioPct = (positions.total_value / positions.initial_portfolio * 100).toFixed(1);
let html = `
<div class="positions-summary">
<div class="position-total">
<div class="total-label">Total Position Value</div>
<div class="total-value ${totalValueClass}">$${positions.total_value.toLocaleString()}</div>
<div class="total-sub">${portfolioPct}% of portfolio</div>
</div>
</div>
`;
const positionsHtml = positions.positions.map(pos => {
const kellyPct = (pos.kelly_fraction * 100).toFixed(1);
return `
<div class="position-item">
<div class="position-symbol">${pos.symbol}</div>
<div class="position-details">
<div class="position-detail-row">
<span>Kelly Fraction:</span>
<span>${kellyPct}%</span>
</div>
<div class="position-detail-row">
<span>Est. Value:</span>
<span class="text-blue">$${pos.estimated_value.toLocaleString()}</span>
</div>
</div>
</div>
`;
}).join('');
html += positionsHtml;
content.innerHTML = html;
}
// Update cron jobs widget
function updateCronJobs(cron) {
const content = document.getElementById('cron-content');
if (cron.error) {
content.innerHTML = `<div class="error">${cron.error}</div>`;
return;
}
if (cron.jobs.length === 0) {
content.innerHTML = `<div class="text-muted">No active jobs</div>`;
return;
}
const jobsHtml = cron.jobs.map(job => `
<div class="cron-job">
<div class="cron-name">${job.name}</div>
<div class="cron-details">
<div class="cron-detail-row">
<span>Schedule:</span>
<span>${job.schedule}</span>
</div>
<div class="cron-detail-row">
<span>Next run:</span>
<span class="text-blue">${formatDateTime(job.next_run)}</span>
</div>
<div class="cron-detail-row">
<span>Last run:</span>
<span>${formatDateTime(job.last_run)}</span>
</div>
</div>
</div>
`).join('');
content.innerHTML = jobsHtml;
}
// Update system health widget
function updateSystemHealth(system) {
const content = document.getElementById('system-content');
let html = `\n <div class="system-metrics">\n ${renderDisks(system.disks)}
${formatSystemMetric('Memory', system.memory)}
${formatSystemMetric('CPU', system.cpu)}
</div>\n `;
// Add CPU frequency if available
if (system.cpu_freq) {
html += `\n <div class="cpu-freq-section">\n <div class="freq-row">\n <span class="freq-label">CPU Frequency</span>\n <span class="freq-value">${system.cpu_freq.current}</span>\n <span class="freq-sub">(${system.cpu_freq.ratio} of ${system.cpu_freq.max})</span>\n </div>\n </div>\n `;
}
// Add GPU power section if available
if (system.gpu_power) {
html += `\n <div class="power-section">\n <h3>⚡ Power Usage</h3>\n <div class="power-metrics">\n <div class="power-item">\n <div class="power-label">GPU Power</div>\n <div class="power-value">${system.gpu_power.draw}</div>\n <div class="power-sub">${system.gpu_power.percent} of ${system.gpu_power.limit}</div>\n </div>\n ${system.total_power ? `\n <div class="power-item total">\n <div class="power-label">Total System</div>\n <div class="power-value">${system.total_power.estimated}</div>\n <div class="power-sub">GPU: ${system.total_power.gpu} | CPU: ~${system.total_power.cpu_estimated}</div>\n </div>\n ` : ''}\n </div>\n </div>\n `;
}
content.innerHTML = html;
}
// Render disk metrics (supports multiple disks)
function renderDisks(disks) {
if (!disks || disks.length === 0) return '';
return disks.map(disk => {
const percent = parseInt(disk.percent.replace('%', '')) || 0;
const displayValue = `${disk.used} / ${disk.total}`;
const fillClass = percent > 90 ? 'danger' : percent > 70 ? 'warning' : '';
return `
<div class="metric-row">
<span class="metric-label">${disk.label}${disk.mount !== '/' ? ' (' + disk.mount + ')' : ''}</span>
<div class="metric-bar">
<div class="metric-fill ${fillClass}" style="width: ${percent}%"></div>
</div>
<span class="metric-value">${displayValue} (${disk.percent})</span>
</div>
`;
}).join('');
}
// Format individual system metric
function formatSystemMetric(label, data) {
if (!data) return '';
let percent = 0;
let displayValue = '';
if (label === 'Memory') {
percent = parseInt(data.percent.replace('%', '')) || 0;
displayValue = `${data.used} / ${data.total}`;
} else if (label === 'CPU') {
percent = parseInt(data.usage.replace('%', '')) || 0;
displayValue = data.usage;
}
const fillClass = percent > 90 ? 'danger' : percent > 70 ? 'warning' : '';
return `
<div class="metric-row">
<span class="metric-label">${label}</span>
<div class="metric-bar">
<div class="metric-fill ${fillClass}" style="width: ${percent}%"></div>
</div>
<span class="metric-value">${displayValue}</span>
</div>
`;
}
// Update logs widget
function updateLogs(logs) {
const content = document.getElementById('logs-content');
if (logs.error) {
content.innerHTML = `<div class="error">${logs.error}</div>`;
return;
}
if (logs.logs.length === 0) {
content.innerHTML = `<div class="text-muted">No recent logs</div>`;
return;
}
const logsHtml = logs.logs.map(log => {
let logClass = 'log-info';
if (log.includes('ERROR')) logClass = 'log-error';
else if (log.includes('WARNING')) logClass = 'log-warning';
return `<div class="log-entry ${logClass}">${escapeHtml(log)}</div>`;
}).join('');
content.innerHTML = logsHtml;
}
// Update last updated timestamp
function updateTimestamp(timestamp) {
const element = document.getElementById('last-updated');
element.textContent = `Last updated: ${formatDateTime(timestamp)}`;
}
// Format datetime string
function formatDateTime(dateString) {
if (!dateString || dateString === 'Never' || dateString === 'Unknown') {
return dateString || 'N/A';
}
try {
const date = new Date(dateString);
return date.toLocaleString('en-US', {
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
hour12: true
});
} catch (e) {
return dateString;
}
}
// Escape HTML to prevent XSS
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Auto-refresh every 30 seconds
let autoRefreshInterval;
function startAutoRefresh() {
autoRefreshInterval = setInterval(refreshData, 30000);
}
// Initialize dashboard
document.addEventListener('DOMContentLoaded', () => {
refreshData();
startAutoRefresh();
});