Spaces:
Runtime error
Runtime error
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=1024"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Nakhoda4X Pro - Portfolio Dashboard</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script> | |
<script> | |
tailwind.config = { | |
darkMode: 'class', | |
theme: { | |
extend: { | |
colors: { | |
primary: '#1E90FF', | |
secondary: '#8b5cf6', | |
darkBg: '#111827', | |
darkCard: '#1f2937', | |
} | |
} | |
} | |
} | |
</script> | |
<style> | |
.trading-card:hover { transform: translateY(-5px); box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); } | |
.animate-pulse { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; } | |
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } | |
.coin-animation { animation: float 3s ease-in-out infinite; } | |
@keyframes float { 0% { transform: translateY(0px); } 50% { transform: translateY(-10px); } 100% { transform: translateY(0px); } } | |
.api-form { max-height: 0; overflow: hidden; transition: max-height 0.3s ease-in-out; } | |
.api-form.open { max-height: 200px; } | |
</style> | |
</head> | |
<body class="bg-gray-100 dark:bg-darkBg transition-colors duration-300"> | |
<!-- Header --> | |
<header class="bg-white dark:bg-darkCard shadow-sm"> | |
<div class="container mx-auto px-4 py-4 flex justify-between items-center"> | |
<div class="flex items-center"> | |
<div class="bg-primary w-10 h-10 rounded-xl flex items-center justify-center mr-3 coin-animation"> | |
<i class="fas fa-chart-line text-white text-xl"></i> | |
</div> | |
<h1 class="text-2xl font-bold text-gray-800 dark:text-white">Nakhoda4X <span class="text-primary">Pro</span></h1> | |
</div> | |
<div class="flex items-center space-x-6"> | |
<button id="theme-toggle" class="text-gray-600 dark:text-gray-300"> | |
<i class="fas fa-moon dark:hidden"></i> | |
<i class="fas fa-sun hidden dark:block"></i> | |
</button> | |
<div class="relative"> | |
<i class="fas fa-bell text-gray-600 dark:text-gray-300 text-xl"></i> | |
<span class="absolute -top-2 -right-2 bg-red-500 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center">3</span> | |
</div> | |
<div class="flex items-center"> | |
<div class="mr-3 text-right"> | |
<p class="font-semibold text-gray-800 dark:text-white">SahabatPipHijau</p> | |
<p class="text-sm text-gray-500">Premium Account</p> | |
</div> | |
<div class="h-10 w-10 rounded-full bg-gradient-to-r from-primary to-secondary flex items-center justify-center text-white font-bold"> | |
J | |
</div> | |
</div> | |
</div> | |
</div> | |
</header> | |
<!-- Main Content --> | |
<main class="container mx-auto px-4 py-8"> | |
<!-- Dashboard Header --> | |
<div class="flex justify-between items-center mb-8"> | |
<div> | |
<h2 class="text-3xl font-bold text-gray-800 dark:text-white">Trading Dashboard</h2> | |
<p class="text-gray-500 mt-1 dark:text-gray-400">Connected to BingX via API</p> | |
</div> | |
<div class="flex space-x-4"> | |
<button id="refresh-btn" class="px-4 py-2 bg-white dark:bg-darkCard rounded-lg border border-gray-200 dark:border-gray-700 text-gray-700 dark:text-gray-300 flex items-center"> | |
<i class="fas fa-sync mr-2"></i> Refresh | |
</button> | |
</div> | |
</div> | |
<!-- Stats Cards --> | |
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8"> | |
<div class="trading-card bg-white dark:bg-darkCard rounded-2xl shadow-md p-6 transition-all duration-300"> | |
<div class="flex justify-between"> | |
<div> | |
<p class="text-gray-500 dark:text-gray-400">Total Balance</p> | |
<h3 id="total-balance" class="text-2xl font-bold mt-2 dark:text-white">$0.00</h3> | |
</div> | |
<div class="w-12 h-12 rounded-xl bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center"> | |
<i class="fas fa-wallet text-blue-500 text-xl"></i> | |
</div> | |
</div> | |
<div id="balance-change" class="mt-4 flex items-center text-green-500"> | |
<i class="fas fa-caret-up mr-1"></i> | |
<span class="font-medium">0.0%</span> | |
<span class="text-gray-500 ml-2 dark:text-gray-400">last 24h</span> | |
</div> | |
</div> | |
<div class="trading-card bg-white dark:bg-darkCard rounded-2xl shadow-md p-6 transition-all duration-300"> | |
<div class="flex justify-between"> | |
<div> | |
<p class="text-gray-500 dark:text-gray-400">Open Trades</p> | |
<h3 id="open-trades" class="text-2xl font-bold mt-2 dark:text-white">0</h3> | |
</div> | |
<div class="w-12 h-12 rounded-xl bg-purple-100 dark:bg-purple-900/30 flex items-center justify-center"> | |
<i class="fas fa-exchange-alt text-purple-500 text-xl"></i> | |
</div> | |
</div> | |
<div id="trade-types" class="mt-4 flex items-center text-blue-500"> | |
<span class="font-medium">0 Long</span> | |
<span class="text-gray-500 mx-2 dark:text-gray-400">β’</span> | |
<span class="font-medium">0 Short</span> | |
</div> | |
</div> | |
<div class="trading-card bg-white dark:bg-darkCard rounded-2xl shadow-md p-6 transition-all duration-300"> | |
<div class="flex justify-between"> | |
<div> | |
<p class="text-gray-500 dark:text-gray-400">Today's Profit</p> | |
<h3 id="today-profit" class="text-2xl font-bold mt-2 dark:text-white">$0.00</h3> | |
</div> | |
<div class="w-12 h-12 rounded-xl bg-green-100 dark:bg-green-900/30 flex items-center justify-center"> | |
<i class="fas fa-chart-bar text-green-500 text-xl"></i> | |
</div> | |
</div> | |
<div id="profit-change" class="mt-4 flex items-center text-green-500"> | |
<i class="fas fa-caret-up mr-1"></i> | |
<span class="font-medium">0.0%</span> | |
<span class="text-gray-500 ml-2 dark:text-gray-400">vs yesterday</span> | |
</div> | |
</div> | |
<div class="trading-card bg-white dark:bg-darkCard rounded-2xl shadow-md p-6 transition-all duration-300"> | |
<div class="flex justify-between"> | |
<div> | |
<p class="text-gray-500 dark:text-gray-400">Risk Exposure</p> | |
<h3 id="risk-exposure" class="text-2xl font-bold mt-2 dark:text-white">Low</h3> | |
</div> | |
<div class="w-12 h-12 rounded-xl bg-yellow-100 dark:bg-yellow-900/30 flex items-center justify-center"> | |
<i class="fas fa-exclamation-triangle text-yellow-500 text-xl"></i> | |
</div> | |
</div> | |
<div id="exposure-percent" class="mt-4 flex items-center text-gray-500 dark:text-gray-400"> | |
<span class="font-medium">0%</span> | |
<span class="ml-2">of balance</span> | |
</div> | |
</div> | |
</div> | |
<!-- Charts and Statistics Section --> | |
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8"> | |
<div class="lg:col-span-2 bg-white dark:bg-darkCard rounded-2xl shadow-md p-6"> | |
<div class="flex justify-between items-center mb-6"> | |
<h3 class="text-lg font-bold text-gray-800 dark:text-white">Monthly Performance</h3> | |
<div class="flex space-x-3"> | |
<button class="px-3 py-1 rounded-lg text-sm bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-white">1M</button> | |
<button class="px-3 py-1 rounded-lg text-sm bg-primary text-white">3M</button> | |
<button class="px-3 py-1 rounded-lg text-sm bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-white">6M</button> | |
<button class="px-3 py-1 rounded-lg text-sm bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-white">1Y</button> | |
</div> | |
</div> | |
<canvas id="performanceChart"></canvas> | |
</div> | |
<div class="bg-white dark:bg-darkCard rounded-2xl shadow-md p-6"> | |
<h3 class="text-lg font-bold text-gray-800 dark:text-white mb-6">Advanced Statistics</h3> | |
<div id="advanced-stats" class="space-y-5"> | |
<!-- Dynamically populated --> | |
</div> | |
</div> | |
</div> | |
<!-- Trading Activity --> | |
<div class="bg-white dark:bg-darkCard rounded-2xl shadow-md p-6 mb-8"> | |
<div class="flex justify-between items-center mb-6"> | |
<h3 class="text-lg font-bold text-gray-800 dark:text-white">Trading Activity</h3> | |
<div class="flex space-x-3"> | |
<button class="px-3 py-1 rounded-lg text-sm bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-white">All</button> | |
<button class="px-3 py-1 rounded-lg text-sm bg-primary text-white">Open</button> | |
<button class="px-3 py-1 rounded-lg text-sm bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-white">Closed</button> | |
</div> | |
</div> | |
<div class="overflow-x-auto"> | |
<table class="w-full"> | |
<thead> | |
<tr class="text-left text-gray-500 dark:text-gray-400 text-sm"> | |
<th class="pb-4">Symbol</th> | |
<th class="pb-4">Type</th> | |
<th class="pb-4">Size</th> | |
<th class="pb-4">Entry Price</th> | |
<th class="pb-4">Current Price</th> | |
<th class="pb-4">Profit/Loss</th> | |
<th class="pb-4">Status</th> | |
<th class="pb-4"></th> | |
</tr> | |
</thead> | |
<tbody id="trading-table-body" class="text-sm"> | |
<!-- Dynamically populated --> | |
</tbody> | |
</table> | |
</div> | |
</div> | |
<!-- API Connection Section --> | |
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
<div class="bg-gradient-to-r from-primary to-secondary rounded-2xl p-6"> | |
<div class="flex items-center mb-4"> | |
<div class="mr-4"> | |
<div class="h-12 w-12 rounded-xl bg-white/30 flex items-center justify-center"> | |
<i class="fas fa-plug text-white text-xl"></i> | |
</div> | |
</div> | |
<div> | |
<h3 class="text-lg font-bold text-white">BingX API Connected</h3> | |
<p id="last-sync" class="text-blue-100">Last synced: Just now</p> | |
</div> | |
</div> | |
<!-- API Credentials Form --> | |
<button id="toggle-api-form" class="w-full py-2 bg-white rounded-xl text-primary font-bold flex items-center justify-center mb-2"> | |
<i class="fas fa-cog mr-2"></i> Manage API Credentials | |
</button> | |
<div id="api-form" class="api-form"> | |
<input id="api-key-input" type="text" placeholder="Enter API Key" class="w-full p-2 mb-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-800 dark:text-white"> | |
<input id="api-secret-input" type="text" placeholder="Enter API Secret" class="w-full p-2 mb-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-800 dark:text-white"> | |
<button id="save-api-btn" class="w-full py-2 bg-primary text-white rounded-lg">Save Credentials</button> | |
</div> | |
<button id="sync-now" class="mt-2 w-full py-3 bg-white rounded-xl text-primary font-bold flex items-center justify-center"> | |
<i class="fas fa-sync mr-2"></i> Sync Now | |
</button> | |
</div> | |
<div class="lg:col-span-2 bg-white dark:bg-darkCard rounded-2xl shadow-md p-6"> | |
<div class="flex justify-between items-center mb-6"> | |
<h3 class="text-lg font-bold text-gray-800 dark:text-white">Portfolio Allocation</h3> | |
<div> | |
<span id="allocation-update" class="text-gray-500 dark:text-gray-400 text-sm">Last updated: Just now</span> | |
</div> | |
</div> | |
<div class="flex items-center"> | |
<div class="w-40 h-40 mr-8"> | |
<canvas id="allocationChart"></canvas> | |
</div> | |
<div id="allocation-legend" class="flex-grow"> | |
<!-- Dynamically populated --> | |
</div> | |
</div> | |
</div> | |
</div> | |
</main> | |
<script> | |
// Dark Mode Toggle | |
const themeToggle = document.getElementById('theme-toggle'); | |
themeToggle.addEventListener('click', () => { | |
document.documentElement.classList.toggle('dark'); | |
}); | |
// Chart Initialization | |
const performanceCtx = document.getElementById('performanceChart').getContext('2d'); | |
const performanceChart = new Chart(performanceCtx, { | |
type: 'line', | |
data: { labels: [], datasets: [{ label: 'Profit/Loss', data: [], borderColor: '#1E90FF', backgroundColor: 'rgba(30, 144, 255, 0.1)', tension: 0.4, fill: true }] }, | |
options: { responsive: true, plugins: { legend: { display: false } }, scales: { y: { grid: { color: 'rgba(0, 0, 0, 0.05)', borderDash: [5] }, ticks: { callback: value => '$' + value } }, x: { grid: { display: false } } } } | |
}); | |
const allocationCtx = document.getElementById('allocationChart').getContext('2d'); | |
const allocationChart = new Chart(allocationCtx, { | |
type: 'doughnut', | |
data: { labels: [], datasets: [{ data: [], backgroundColor: ['#1E90FF', '#10b981', '#8b5cf6', '#f59e0b'], borderWidth: 0 }] }, | |
options: { responsive: true, cutout: '65%', plugins: { legend: { display: false }, tooltip: { callbacks: { label: context => `${context.label}: ${context.parsed}%` } } } } | |
}); | |
// API Configuration | |
let API_KEY = localStorage.getItem('bingxApiKey') || ''; | |
let API_SECRET = localStorage.getItem('bingxApiSecret') || ''; | |
const PROXY_URL = '/api/proxy'; // Proxy endpoint | |
// Toggle API Form | |
const toggleApiFormBtn = document.getElementById('toggle-api-form'); | |
const apiForm = document.getElementById('api-form'); | |
toggleApiFormBtn.addEventListener('click', () => { | |
apiForm.classList.toggle('open'); | |
}); | |
// Save API Credentials | |
const saveApiBtn = document.getElementById('save-api-btn'); | |
saveApiBtn.addEventListener('click', () => { | |
const newApiKey = document.getElementById('api-key-input').value.trim(); | |
const newApiSecret = document.getElementById('api-secret-input').value.trim(); | |
if (newApiKey && newApiSecret) { | |
API_KEY = newApiKey; | |
API_SECRET = newApiSecret; | |
localStorage.setItem('bingxApiKey', API_KEY); | |
localStorage.setItem('bingxApiSecret', API_SECRET); | |
alert('API credentials saved successfully! Syncing data...'); | |
fetchData(); | |
apiForm.classList.remove('open'); | |
} else { | |
alert('Please enter both API Key and Secret.'); | |
} | |
}); | |
// Fetch from Proxy | |
async function fetchFromProxy(endpoint, params = {}) { | |
if (!API_KEY || !API_SECRET) { | |
throw new Error('API Key and Secret are not set. Please enter your credentials.'); | |
} | |
params.recvWindow = params.recvWindow || 5000; | |
const url = `${PROXY_URL}?endpoint=${encodeURIComponent(endpoint)}&${new URLSearchParams(params).toString()}`; | |
const response = await fetch(url); | |
if (!response.ok) { | |
const error = await response.json(); | |
throw new Error(`API Error ${response.status}: ${error.error || 'Unknown error'}`); | |
} | |
return response.json(); | |
} | |
// Fetch Specific Data | |
async function fetchBalance() { | |
const data = await fetchFromProxy('/openApi/swap/v3/user/balance'); | |
return data.data.find(b => b.asset === 'USDT')?.walletBalance || 0; | |
} | |
async function fetchOpenPositions() { | |
const data = await fetchFromProxy('/openApi/swap/v2/user/positions', { symbol: 'BTC-USDT' }); | |
return data.data || []; | |
} | |
async function fetchTradeHistory() { | |
const data = await fetchFromProxy('/openApi/swap/v2/user/income', { limit: 100 }); | |
return data.data || []; | |
} | |
// Helper Functions | |
function calculateTodayProfit(trades) { | |
const today = new Date().toDateString(); | |
return trades.filter(trade => new Date(trade.time).toDateString() === today) | |
.reduce((sum, trade) => sum + (trade.income || 0), 0); | |
} | |
function calculateAdvancedStats(trades) { | |
let totalProfit = 0, totalLoss = 0, wins = 0; | |
trades.forEach(trade => { | |
const pl = trade.income || 0; | |
if (pl > 0) { totalProfit += pl; wins++; } else { totalLoss += Math.abs(pl); } | |
}); | |
const profitFactor = totalLoss ? (totalProfit / totalLoss) : 0; | |
const winRate = trades.length ? (wins / trades.length * 100) : 0; | |
return { profitFactor: profitFactor.toFixed(2), winRate: winRate.toFixed(1) }; | |
} | |
function calculatePortfolioAllocation(positions) { | |
const totalValue = positions.reduce((sum, pos) => sum + (pos.positionValue || 0), 0); | |
const bySymbol = positions.reduce((acc, pos) => { | |
acc[pos.symbol] = (acc[pos.symbol] || 0) + (pos.positionValue || 0); | |
return acc; | |
}, {}); | |
return { labels: Object.keys(bySymbol), data: Object.values(bySymbol).map(val => totalValue ? (val / totalValue * 100) : 0) }; | |
} | |
// Update UI Functions | |
function updateTradingTable(positions, trades) { | |
const tbody = document.getElementById('trading-table-body'); | |
tbody.innerHTML = ''; | |
positions.forEach(pos => { | |
tbody.innerHTML += ` | |
<tr class="border-b border-gray-200 dark:border-gray-700"> | |
<td class="py-4">${pos.symbol}</td> | |
<td class="py-4"><span class="${pos.positionSide === 'LONG' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'} px-2 py-1 rounded">${pos.positionSide}</span></td> | |
<td class="py-4">${pos.quantity}</td> | |
<td class="py-4">$${pos.entryPrice.toFixed(2)}</td> | |
<td class="py-4 font-medium">$${pos.markPrice.toFixed(2)}</td> | |
<td class="py-4 font-bold ${pos.unrealizedProfit > 0 ? 'text-green-500' : 'text-red-500'}">$${pos.unrealizedProfit.toFixed(2)}</td> | |
<td class="py-4"><span class="px-2 py-1 rounded bg-blue-100 text-blue-800">Open</span></td> | |
<td class="py-4 text-right"><button class="text-gray-400 hover:text-primary"><i class="fas fa-ellipsis-v"></i></button></td> | |
</tr>`; | |
}); | |
trades.slice(0, 5).forEach(trade => { | |
tbody.innerHTML += ` | |
<tr class="border-b border-gray-200 dark:border-gray-700"> | |
<td class="py-4">${trade.symbol}</td> | |
<td class="py-4"><span class="${trade.positionSide === 'LONG' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'} px-2 py-1 rounded">${trade.positionSide}</span></td> | |
<td class="py-4">${trade.quantity || 0}</td> | |
<td class="py-4">$${trade.entryPrice?.toFixed(2) || '0.00'}</td> | |
<td class="py-4 font-medium">$${trade.exitPrice?.toFixed(2) || '0.00'}</td> | |
<td class="py-4 font-bold ${trade.income > 0 ? 'text-green-500' : 'text-red-500'}">$${trade.income?.toFixed(2) || '0.00'}</td> | |
<td class="py-4"><span class="px-2 py-1 rounded bg-gray-100 text-gray-800">Closed</span></td> | |
<td class="py-4 text-right"><button class="text-gray-400 hover:text-primary"><i class="fas fa-ellipsis-v"></i></button></td> | |
</tr>`; | |
}); | |
} | |
function updateAdvancedStats(stats) { | |
document.getElementById('advanced-stats').innerHTML = ` | |
<div> | |
<div class="flex justify-between mb-1"><span class="text-gray-500 dark:text-gray-400">Profit Factor</span><span class="font-bold text-green-500">${stats.profitFactor}</span></div> | |
<div class="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700"><div class="bg-green-600 h-2 rounded-full" style="width: ${Math.min(stats.profitFactor * 25, 100)}%"></div></div> | |
</div> | |
<div> | |
<div class="flex justify-between mb-1"><span class="text-gray-500 dark:text-gray-400">Win Rate</span><span class="font-bold text-purple-500">${stats.winRate}%</span></div> | |
<div class="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700"><div class="bg-purple-600 h-2 rounded-full" style="width: ${stats.winRate}%"></div></div> | |
</div>`; | |
} | |
function updatePerformanceChart(trades) { | |
const monthlyPL = trades.reduce((acc, trade) => { | |
const month = new Date(trade.time).toLocaleString('default', { month: 'short' }); | |
acc[month] = (acc[month] || 0) + (trade.income || 0); | |
return acc; | |
}, {}); | |
performanceChart.data.labels = Object.keys(monthlyPL); | |
performanceChart.data.datasets[0].data = Object.values(monthlyPL); | |
performanceChart.update(); | |
} | |
function updateAllocationChart(allocation) { | |
allocationChart.data.labels = allocation.labels; | |
allocationChart.data.datasets[0].data = allocation.data; | |
allocationChart.update(); | |
const legend = document.getElementById('allocation-legend'); | |
legend.innerHTML = allocation.labels.map((label, i) => ` | |
<div class="mb-3"> | |
<div class="flex justify-between mb-1"> | |
<span class="text-gray-500 dark:text-gray-400 flex items-center"> | |
<span class="h-3 w-3 bg-[${allocationChart.data.datasets[0].backgroundColor[i]}] rounded-full mr-2"></span> | |
${label} | |
</span> | |
<span class="font-medium dark:text-white">${allocation.data[i].toFixed(1)}%</span> | |
</div> | |
<div class="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700"> | |
<div class="bg-[${allocationChart.data.datasets[0].backgroundColor[i]}] h-2 rounded-full" style="width: ${allocation.data[i]}%"></div> | |
</div> | |
</div>`).join(''); | |
} | |
// Main Data Fetch Function | |
async function fetchData() { | |
try { | |
document.querySelectorAll('.trading-card h3').forEach(el => el.textContent = 'Loading...'); | |
document.getElementById('trading-table-body').innerHTML = '<tr><td colspan="8" class="py-4 text-center">Loading...</td></tr>'; | |
if (!API_KEY || !API_SECRET) { | |
throw new Error('Please enter your BingX API Key and Secret in the credentials form.'); | |
} | |
const [balance, positions, trades] = await Promise.all([ | |
fetchBalance(), | |
fetchOpenPositions(), | |
fetchTradeHistory() | |
]); | |
document.getElementById('total-balance').textContent = `$${balance.toFixed(2)}`; | |
document.getElementById('open-trades').textContent = positions.length; | |
const longCount = positions.filter(p => p.positionSide === 'LONG').length; | |
document.getElementById('trade-types').innerHTML = `<span class="font-medium">${longCount} Long</span><span class="text-gray-500 mx-2 dark:text-gray-400">β’</span><span class="font-medium">${positions.length - longCount} Short</span>`; | |
const todayProfit = calculateTodayProfit(trades); | |
document.getElementById('today-profit').textContent = `$${todayProfit.toFixed(2)}`; | |
const riskPercent = balance ? (positions.reduce((sum, p) => sum + (p.positionValue || 0), 0) / balance * 100) : 0; | |
document.getElementById('risk-exposure').textContent = riskPercent < 20 ? 'Low' : riskPercent < 50 ? 'Medium' : 'High'; | |
document.getElementById('exposure-percent').innerHTML = `<span class="font-medium">${riskPercent.toFixed(1)}%</span><span class="ml-2">of balance</span>`; | |
updateTradingTable(positions, trades); | |
const stats = calculateAdvancedStats(trades); | |
updateAdvancedStats(stats); | |
updatePerformanceChart(trades); | |
const allocation = calculatePortfolioAllocation(positions); | |
updateAllocationChart(allocation); | |
document.getElementById('last-sync').textContent = `Last synced: ${new Date().toLocaleTimeString()}`; | |
document.getElementById('allocation-update').textContent = `Last updated: ${new Date().toLocaleTimeString()}`; | |
} catch (error) { | |
console.error('Error fetching data:', error); | |
alert(`Failed to sync with BingX API: ${error.message}. Please check your credentials, permissions, network, or BingX API documentation.`); | |
} | |
} | |
// Event Listeners | |
document.getElementById('refresh-btn').addEventListener('click', fetchData); | |
document.getElementById('sync-now').addEventListener('click', fetchData); | |
window.addEventListener('load', fetchData); | |
setInterval(fetchData, 120000); | |
</script> | |
</body> | |
</html> |