Files
quantumbotx/static/js/dashboard.js
T
Reynov Christian 348acc6f43 feat: Add Quantum Velocity strategy and major stability overhaul" -m "
This major commit introduces a new advanced trading strategy, resolves critical trading logic errors, and significantly enhances system stability and UI data accuracy.

Key Changes:

- **New Features & Strategies**:
  - **Quantum Velocity Strategy**: Added 'quantum_velocity.py', a new hybrid strategy combining a long-term trend filter (EMA 200) with a volatility-based entry trigger (Bollinger Squeeze).
  - **Hybrid Pro Backtester**: Added 'lab/backtester_hybrid_pro.py' to facilitate advanced, offline testing and optimization of the QuantumBotX Hybrid strategy.

- **Critical Trading Logic Fixes**:
  - Resolved 'Invalid Stops' and 'Unsupported filling mode' errors by refactoring 'core/mt5/trade.py' to use dynamic point calculation and the FOK fill policy.
  - Fixed a recurring 'KeyError' in 'BollingerSqueezeStrategy' by ensuring consistent naming for Bollinger Bands columns.

- **System Stability & Robustness**:
  - Hardened the graceful shutdown handler in 'app.py' to prevent crashes from repeated Ctrl+C signals, ensuring a clean shutdown process.
  - Enhanced the notification system by adding an 'is_notification' flag to logs, allowing for a clear distinction between critical alerts and general activity.

- **Dashboard & UI Enhancements**:
  - Fixed the 'Total Bots' card on the dashboard by correcting the backend API ('api_dashboard.py') to send the complete stats payload.
  - Replaced static '0' values on dashboard cards with loading spinners for a more professional user experience.
  - The 'Create/Edit Bot' modal button now dynamically changes its text to 'Ubah Bot' when in edit mode.
2025-08-01 22:47:37 +08:00

194 lines
6.7 KiB
JavaScript

// --- PUSAT KONTROL DASHBOARD ---
// Formatter untuk nilai USD
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
// Update statistik MT5 di dashboard
async function updateDashboardStats() {
try {
const response = await fetch('/api/dashboard/stats');
if (!response.ok) throw new Error('Gagal mengambil statistik dasbor');
const stats = await response.json();
document.getElementById('total-equity').textContent = formatter.format(stats.equity);
document.getElementById('todays-profit').textContent = formatter.format(stats.todays_profit);
document.getElementById('active-bots-count').textContent = stats.active_bots_count;
document.getElementById('total-bots-count').textContent = stats.total_bots;
// Warna profit/loss
const profitEl = document.getElementById('todays-profit');
if (profitEl) {
profitEl.classList.remove('text-green-500', 'text-red-500');
profitEl.classList.add(stats.todays_profit < 0 ? 'text-red-500' : 'text-green-500');
}
} catch (error) {
console.error('[DashboardStats] Error:', error);
}
}
// Tampilkan daftar bot aktif
async function fetchAllBots() {
try {
const response = await fetch('/api/bots');
if (!response.ok) throw new Error('Gagal mengambil daftar bot');
const bots = await response.json();
const listEl = document.getElementById('active-bots-list');
listEl.innerHTML = '';
const activeBots = bots.filter(bot => bot.status === 'Aktif');
if (activeBots.length === 0) {
listEl.innerHTML = '<p class="p-4 text-gray-500">Tidak ada bot yang sedang aktif.</p>';
} else {
const botsHtml = activeBots.map(bot => {
const badge = bot.strategy === 'MERCY_EDGE'
? `<span class="ml-2 px-2 py-0.5 text-xs bg-purple-100 text-purple-800 rounded-full">AI</span>`
: '';
return `
<div class="p-4 hover:bg-gray-50 transition">
<div class="flex items-center justify-between">
<div class="flex items-center">
<div class="w-10 h-10 rounded-full bg-green-100 flex items-center justify-center text-green-600">
<i class="fas fa-robot"></i>
</div>
<div class="ml-3">
<p class="font-medium text-gray-800">
${bot.name} ${badge}
</p>
<p class="text-xs text-gray-500">${bot.market}</p>
</div>
</div>
<div class="text-right">
<p class="text-xs text-gray-500">
Running <span class="inline-block w-2 h-2 rounded-full bg-green-500 ml-1"></span>
</p>
</div>
</div>
</div>
`;
}).join('');
listEl.innerHTML = botsHtml;
}
} catch (error) {
console.error('[FetchBots] Error:', error);
}
}
// Grafik harga
let priceChart;
async function updatePriceChart(symbol = 'EURUSD') {
try {
const response = await fetch(`/api/chart/data?symbol=${symbol}`);
const chartData = await response.json();
const ctx = document.getElementById('priceChart');
const config = {
type: 'line',
data: {
labels: chartData.labels,
datasets: [{
label: symbol,
data: chartData.data,
borderColor: '#3B82F6',
backgroundColor: 'rgba(59, 130, 246, 0.05)',
borderWidth: 2,
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
y: { beginAtZero: false },
x: { grid: { display: false } }
}
}
};
if (priceChart) {
priceChart.data.labels = chartData.labels;
priceChart.data.datasets[0].data = chartData.data;
priceChart.update();
} else {
priceChart = new Chart(ctx, config); // eslint-disable-line no-undef
}
} catch (error) {
console.error('[Chart] Gagal update grafik:', error);
}
}
// Grafik RSI
let rsiChart;
async function updateRsiChart(symbol = 'EURUSD') {
try {
const response = await fetch(`/api/rsi_data?symbol=${symbol}&timeframe=H1`);
const rsiData = await response.json();
const ctx = document.getElementById('rsiChart');
const config = {
type: 'line',
data: {
labels: rsiData.timestamps,
datasets: [{
label: 'RSI',
data: rsiData.rsi_values,
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderWidth: 2,
tension: 0.4
}]
},
options: {
responsive: true,
plugins: { legend: { display: false } },
scales: {
y: {
min: 0,
max: 100,
ticks: { stepSize: 10 },
grid: { color: 'rgba(0,0,0,0.05)' }
}
}
}
};
if (rsiChart) {
rsiChart.data.labels = rsiData.timestamps;
rsiChart.data.datasets[0].data = rsiData.rsi_values;
rsiChart.update();
} else {
rsiChart = new Chart(ctx, config); // eslint-disable-line no-undef
}
} catch (error) {
console.error('[RSI Chart] Gagal update grafik RSI:', error);
const ctx = document.getElementById('rsiChart');
if (ctx && ctx.parentElement) {
ctx.parentElement.innerHTML = '<p class="text-center text-red-500">Gagal memuat data RSI.</p>';
}
}
}
// DOM ready
document.addEventListener('DOMContentLoaded', () => {
updateDashboardStats();
fetchAllBots();
updatePriceChart();
updateRsiChart();
// Interval refresh
setInterval(updateDashboardStats, 10000);
setInterval(fetchAllBots, 5000);
});