EricSam commited on
Commit
91283f9
Β·
verified Β·
1 Parent(s): b00fdc8

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +225 -258
index.html CHANGED
@@ -10,19 +10,230 @@
10
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
11
  <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
12
  <script>
13
- tailwind.config = {
14
- darkMode: 'class',
15
- theme: {
16
- extend: {
17
- colors: {
18
- primary: '#1E90FF',
19
- secondary: '#8b5cf6',
20
- darkBg: '#111827',
21
- darkCard: '#1f2937',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  }
 
 
 
 
23
  }
 
 
 
 
24
  }
25
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  </script>
27
  <style>
28
  .trading-card:hover { transform: translateY(-5px); box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); }
@@ -35,7 +246,6 @@
35
  </style>
36
  </head>
37
  <body class="bg-gray-100 dark:bg-darkBg transition-colors duration-300">
38
- <!-- Header -->
39
  <header class="bg-white dark:bg-darkCard shadow-sm">
40
  <div class="container mx-auto px-4 py-4 flex justify-between items-center">
41
  <div class="flex items-center">
@@ -65,10 +275,7 @@
65
  </div>
66
  </div>
67
  </header>
68
-
69
- <!-- Main Content -->
70
  <main class="container mx-auto px-4 py-8">
71
- <!-- Dashboard Header -->
72
  <div class="flex justify-between items-center mb-8">
73
  <div>
74
  <h2 class="text-3xl font-bold text-gray-800 dark:text-white">Trading Dashboard</h2>
@@ -83,8 +290,6 @@
83
  </button>
84
  </div>
85
  </div>
86
-
87
- <!-- Stats Cards -->
88
  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
89
  <div class="trading-card bg-white dark:bg-darkCard rounded-2xl shadow-md p-6 transition-all duration-300">
90
  <div class="flex justify-between">
@@ -150,8 +355,6 @@
150
  </div>
151
  </div>
152
  </div>
153
-
154
- <!-- Charts and Statistics Section -->
155
  <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
156
  <div class="lg:col-span-2 bg-white dark:bg-darkCard rounded-2xl shadow-md p-6">
157
  <div class="flex justify-between items-center mb-6">
@@ -172,8 +375,6 @@
172
  </div>
173
  </div>
174
  </div>
175
-
176
- <!-- Trading Activity -->
177
  <div class="bg-white dark:bg-darkCard rounded-2xl shadow-md p-6 mb-8">
178
  <div class="flex justify-between items-center mb-6">
179
  <h3 class="text-lg font-bold text-gray-800 dark:text-white">Trading Activity</h3>
@@ -203,8 +404,6 @@
203
  </table>
204
  </div>
205
  </div>
206
-
207
- <!-- API Connection Section -->
208
  <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
209
  <div class="bg-gradient-to-r from-primary to-secondary rounded-2xl p-6">
210
  <div class="flex items-center mb-4">
@@ -218,14 +417,14 @@
218
  <p id="last-sync" class="text-blue-100">Last synced: Just now</p>
219
  </div>
220
  </div>
221
- <!-- API Credentials Form -->
222
  <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">
223
  <i class="fas fa-cog mr-2"></i> Manage API Credentials
224
  </button>
225
  <div id="api-form" class="api-form">
226
- <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">
227
- <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">
228
- <button id="save-api-btn" class="w-full py-2 bg-primary text-white rounded-lg">Save Credentials</button>
 
229
  </div>
230
  <button id="sync-now" class="mt-2 w-full py-3 bg-white rounded-xl text-primary font-bold flex items-center justify-center">
231
  <i class="fas fa-sync mr-2"></i> Sync Now
@@ -248,239 +447,7 @@
248
  </div>
249
  </div>
250
  </div>
 
251
  </main>
252
-
253
- <script>
254
- // Dark Mode Toggle
255
- const themeToggle = document.getElementById('theme-toggle');
256
- themeToggle.addEventListener('click', () => {
257
- document.documentElement.classList.toggle('dark');
258
- });
259
-
260
- // Chart Initialization
261
- const performanceCtx = document.getElementById('performanceChart').getContext('2d');
262
- const performanceChart = new Chart(performanceCtx, {
263
- type: 'line',
264
- data: { labels: [], datasets: [{ label: 'Profit/Loss', data: [], borderColor: '#1E90FF', backgroundColor: 'rgba(30, 144, 255, 0.1)', tension: 0.4, fill: true }] },
265
- 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 } } } }
266
- });
267
-
268
- const allocationCtx = document.getElementById('allocationChart').getContext('2d');
269
- const allocationChart = new Chart(allocationCtx, {
270
- type: 'doughnut',
271
- data: { labels: [], datasets: [{ data: [], backgroundColor: ['#1E90FF', '#10b981', '#8b5cf6', '#f59e0b'], borderWidth: 0 }] },
272
- options: { responsive: true, cutout: '65%', plugins: { legend: { display: false }, tooltip: { callbacks: { label: context => `${context.label}: ${context.parsed}%` } } } }
273
- });
274
-
275
- // API Configuration
276
- let API_KEY = localStorage.getItem('bingxApiKey') || '';
277
- let API_SECRET = localStorage.getItem('bingxApiSecret') || '';
278
- const PROXY_URL = '/api/proxy'; // Proxy endpoint
279
-
280
- // Toggle API Form
281
- const toggleApiFormBtn = document.getElementById('toggle-api-form');
282
- const apiForm = document.getElementById('api-form');
283
- toggleApiFormBtn.addEventListener('click', () => {
284
- apiForm.classList.toggle('open');
285
- });
286
-
287
- // Save API Credentials
288
- const saveApiBtn = document.getElementById('save-api-btn');
289
- saveApiBtn.addEventListener('click', () => {
290
- const newApiKey = document.getElementById('api-key-input').value.trim();
291
- const newApiSecret = document.getElementById('api-secret-input').value.trim();
292
- if (newApiKey && newApiSecret) {
293
- API_KEY = newApiKey;
294
- API_SECRET = newApiSecret;
295
- localStorage.setItem('bingxApiKey', API_KEY);
296
- localStorage.setItem('bingxApiSecret', API_SECRET);
297
- alert('API credentials saved successfully! Syncing data...');
298
- fetchData();
299
- apiForm.classList.remove('open');
300
- } else {
301
- alert('Please enter both API Key and Secret.');
302
- }
303
- });
304
-
305
- // Fetch from Proxy
306
- async function fetchFromProxy(endpoint, params = {}) {
307
- if (!API_KEY || !API_SECRET) {
308
- throw new Error('API Key and Secret are not set. Please enter your credentials.');
309
- }
310
- params.recvWindow = params.recvWindow || 5000;
311
- const url = `${PROXY_URL}?endpoint=${encodeURIComponent(endpoint)}&${new URLSearchParams(params).toString()}`;
312
- const response = await fetch(url);
313
- if (!response.ok) {
314
- const error = await response.json();
315
- throw new Error(`API Error ${response.status}: ${error.error || 'Unknown error'}`);
316
- }
317
- return response.json();
318
- }
319
-
320
- // Fetch Specific Data
321
- async function fetchBalance() {
322
- const data = await fetchFromProxy('/openApi/swap/v3/user/balance');
323
- return data.data.find(b => b.asset === 'USDT')?.walletBalance || 0;
324
- }
325
-
326
- async function fetchOpenPositions() {
327
- const data = await fetchFromProxy('/openApi/swap/v2/user/positions', { symbol: 'BTC-USDT' });
328
- return data.data || [];
329
- }
330
-
331
- async function fetchTradeHistory() {
332
- const data = await fetchFromProxy('/openApi/swap/v2/user/income', { limit: 100 });
333
- return data.data || [];
334
- }
335
-
336
- // Helper Functions
337
- function calculateTodayProfit(trades) {
338
- const today = new Date().toDateString();
339
- return trades.filter(trade => new Date(trade.time).toDateString() === today)
340
- .reduce((sum, trade) => sum + (trade.income || 0), 0);
341
- }
342
-
343
- function calculateAdvancedStats(trades) {
344
- let totalProfit = 0, totalLoss = 0, wins = 0;
345
- trades.forEach(trade => {
346
- const pl = trade.income || 0;
347
- if (pl > 0) { totalProfit += pl; wins++; } else { totalLoss += Math.abs(pl); }
348
- });
349
- const profitFactor = totalLoss ? (totalProfit / totalLoss) : 0;
350
- const winRate = trades.length ? (wins / trades.length * 100) : 0;
351
- return { profitFactor: profitFactor.toFixed(2), winRate: winRate.toFixed(1) };
352
- }
353
-
354
- function calculatePortfolioAllocation(positions) {
355
- const totalValue = positions.reduce((sum, pos) => sum + (pos.positionValue || 0), 0);
356
- const bySymbol = positions.reduce((acc, pos) => {
357
- acc[pos.symbol] = (acc[pos.symbol] || 0) + (pos.positionValue || 0);
358
- return acc;
359
- }, {});
360
- return { labels: Object.keys(bySymbol), data: Object.values(bySymbol).map(val => totalValue ? (val / totalValue * 100) : 0) };
361
- }
362
-
363
- // Update UI Functions
364
- function updateTradingTable(positions, trades) {
365
- const tbody = document.getElementById('trading-table-body');
366
- tbody.innerHTML = '';
367
- positions.forEach(pos => {
368
- tbody.innerHTML += `
369
- <tr class="border-b border-gray-200 dark:border-gray-700">
370
- <td class="py-4">${pos.symbol}</td>
371
- <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>
372
- <td class="py-4">${pos.quantity}</td>
373
- <td class="py-4">$${pos.entryPrice.toFixed(2)}</td>
374
- <td class="py-4 font-medium">$${pos.markPrice.toFixed(2)}</td>
375
- <td class="py-4 font-bold ${pos.unrealizedProfit > 0 ? 'text-green-500' : 'text-red-500'}">$${pos.unrealizedProfit.toFixed(2)}</td>
376
- <td class="py-4"><span class="px-2 py-1 rounded bg-blue-100 text-blue-800">Open</span></td>
377
- <td class="py-4 text-right"><button class="text-gray-400 hover:text-primary"><i class="fas fa-ellipsis-v"></i></button></td>
378
- </tr>`;
379
- });
380
- trades.slice(0, 5).forEach(trade => {
381
- tbody.innerHTML += `
382
- <tr class="border-b border-gray-200 dark:border-gray-700">
383
- <td class="py-4">${trade.symbol}</td>
384
- <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>
385
- <td class="py-4">${trade.quantity || 0}</td>
386
- <td class="py-4">$${trade.entryPrice?.toFixed(2) || '0.00'}</td>
387
- <td class="py-4 font-medium">$${trade.exitPrice?.toFixed(2) || '0.00'}</td>
388
- <td class="py-4 font-bold ${trade.income > 0 ? 'text-green-500' : 'text-red-500'}">$${trade.income?.toFixed(2) || '0.00'}</td>
389
- <td class="py-4"><span class="px-2 py-1 rounded bg-gray-100 text-gray-800">Closed</span></td>
390
- <td class="py-4 text-right"><button class="text-gray-400 hover:text-primary"><i class="fas fa-ellipsis-v"></i></button></td>
391
- </tr>`;
392
- });
393
- }
394
-
395
- function updateAdvancedStats(stats) {
396
- document.getElementById('advanced-stats').innerHTML = `
397
- <div>
398
- <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>
399
- <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>
400
- </div>
401
- <div>
402
- <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>
403
- <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>
404
- </div>`;
405
- }
406
-
407
- function updatePerformanceChart(trades) {
408
- const monthlyPL = trades.reduce((acc, trade) => {
409
- const month = new Date(trade.time).toLocaleString('default', { month: 'short' });
410
- acc[month] = (acc[month] || 0) + (trade.income || 0);
411
- return acc;
412
- }, {});
413
- performanceChart.data.labels = Object.keys(monthlyPL);
414
- performanceChart.data.datasets[0].data = Object.values(monthlyPL);
415
- performanceChart.update();
416
- }
417
-
418
- function updateAllocationChart(allocation) {
419
- allocationChart.data.labels = allocation.labels;
420
- allocationChart.data.datasets[0].data = allocation.data;
421
- allocationChart.update();
422
- const legend = document.getElementById('allocation-legend');
423
- legend.innerHTML = allocation.labels.map((label, i) => `
424
- <div class="mb-3">
425
- <div class="flex justify-between mb-1">
426
- <span class="text-gray-500 dark:text-gray-400 flex items-center">
427
- <span class="h-3 w-3 bg-[${allocationChart.data.datasets[0].backgroundColor[i]}] rounded-full mr-2"></span>
428
- ${label}
429
- </span>
430
- <span class="font-medium dark:text-white">${allocation.data[i].toFixed(1)}%</span>
431
- </div>
432
- <div class="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700">
433
- <div class="bg-[${allocationChart.data.datasets[0].backgroundColor[i]}] h-2 rounded-full" style="width: ${allocation.data[i]}%"></div>
434
- </div>
435
- </div>`).join('');
436
- }
437
-
438
- // Main Data Fetch Function
439
- async function fetchData() {
440
- try {
441
- document.querySelectorAll('.trading-card h3').forEach(el => el.textContent = 'Loading...');
442
- document.getElementById('trading-table-body').innerHTML = '<tr><td colspan="8" class="py-4 text-center">Loading...</td></tr>';
443
-
444
- if (!API_KEY || !API_SECRET) {
445
- throw new Error('Please enter your BingX API Key and Secret in the credentials form.');
446
- }
447
-
448
- const [balance, positions, trades] = await Promise.all([
449
- fetchBalance(),
450
- fetchOpenPositions(),
451
- fetchTradeHistory()
452
- ]);
453
-
454
- document.getElementById('total-balance').textContent = `$${balance.toFixed(2)}`;
455
- document.getElementById('open-trades').textContent = positions.length;
456
- const longCount = positions.filter(p => p.positionSide === 'LONG').length;
457
- 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>`;
458
- const todayProfit = calculateTodayProfit(trades);
459
- document.getElementById('today-profit').textContent = `$${todayProfit.toFixed(2)}`;
460
- const riskPercent = balance ? (positions.reduce((sum, p) => sum + (p.positionValue || 0), 0) / balance * 100) : 0;
461
- document.getElementById('risk-exposure').textContent = riskPercent < 20 ? 'Low' : riskPercent < 50 ? 'Medium' : 'High';
462
- document.getElementById('exposure-percent').innerHTML = `<span class="font-medium">${riskPercent.toFixed(1)}%</span><span class="ml-2">of balance</span>`;
463
-
464
- updateTradingTable(positions, trades);
465
- const stats = calculateAdvancedStats(trades);
466
- updateAdvancedStats(stats);
467
- updatePerformanceChart(trades);
468
- const allocation = calculatePortfolioAllocation(positions);
469
- updateAllocationChart(allocation);
470
-
471
- document.getElementById('last-sync').textContent = `Last synced: ${new Date().toLocaleTimeString()}`;
472
- document.getElementById('allocation-update').textContent = `Last updated: ${new Date().toLocaleTimeString()}`;
473
- } catch (error) {
474
- console.error('Error fetching data:', error);
475
- alert(`Failed to sync with BingX API: ${error.message}. Please check your credentials, permissions, network, or BingX API documentation.`);
476
- }
477
- }
478
-
479
- // Event Listeners
480
- document.getElementById('refresh-btn').addEventListener('click', fetchData);
481
- document.getElementById('sync-now').addEventListener('click', fetchData);
482
- window.addEventListener('load', fetchData);
483
- setInterval(fetchData, 120000);
484
- </script>
485
  </body>
486
  </html>
 
10
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
11
  <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
12
  <script>
13
+ // Access environment variables from Hugging Face Static Space
14
+ const { BINGX_API_KEY, BINGX_API_SECRET } = window.huggingface.variables || {};
15
+ const API_BASE_URL = "https://open-api.bingx.com";
16
+
17
+ // Generate Signature
18
+ function generateSignature(apiSecret, paramsStr) {
19
+ return CryptoJS.HmacSHA256(paramsStr, apiSecret).toString(CryptoJS.enc.Hex);
20
+ }
21
+
22
+ // Parse Parameters
23
+ function parseParams(params) {
24
+ const sortedKeys = Object.keys(params).sort();
25
+ const paramPairs = sortedKeys.map(key => `${key}=${params[key]}`);
26
+ const paramsStr = paramPairs.join('&');
27
+ return paramsStr ? `${paramsStr}&timestamp=${Date.now()}` : `timestamp=${Date.now()}`;
28
+ }
29
+
30
+ // Fetch from BingX API
31
+ async function fetchFromAPI(endpoint, params = {}) {
32
+ if (!BINGX_API_KEY || !BINGX_API_SECRET) {
33
+ throw new Error('BingX API Key and Secret are not set. Please configure them in the Hugging Face Space Secrets.');
34
+ }
35
+ params.recvWindow = params.recvWindow || 5000;
36
+ const paramsStr = parseParams(params);
37
+ const signature = generateSignature(BINGX_API_SECRET, paramsStr);
38
+ const url = `${API_BASE_URL}${endpoint}?${paramsStr}&signature=${signature}`;
39
+ try {
40
+ const response = await fetch(url, {
41
+ method: 'GET',
42
+ headers: {
43
+ 'X-BX-APIKEY': BINGX_API_KEY,
44
+ 'Content-Type': 'application/json'
45
  }
46
+ });
47
+ if (!response.ok) {
48
+ const errorText = await response.text();
49
+ throw new Error(`API Error ${response.status}: ${errorText}`);
50
  }
51
+ return response.json();
52
+ } catch (error) {
53
+ console.error('Fetch error:', error);
54
+ throw error;
55
  }
56
  }
57
+
58
+ // Fetch Specific Data
59
+ async function fetchBalance() {
60
+ const data = await fetchFromAPI('/openApi/swap/v3/user/balance');
61
+ return data.data.find(b => b.asset === 'USDT')?.walletBalance || 0;
62
+ }
63
+
64
+ async function fetchOpenPositions() {
65
+ const data = await fetchFromAPI('/openApi/swap/v2/user/positions', { symbol: 'BTC-USDT' });
66
+ return data.data || [];
67
+ }
68
+
69
+ async function fetchTradeHistory() {
70
+ const data = await fetchFromAPI('/openApi/swap/v2/user/income', { limit: 100 });
71
+ return data.data || [];
72
+ }
73
+
74
+ // Helper Functions
75
+ function calculateTodayProfit(trades) {
76
+ const today = new Date().toDateString();
77
+ return trades.filter(trade => new Date(trade.time).toDateString() === today)
78
+ .reduce((sum, trade) => sum + (trade.income || 0), 0);
79
+ }
80
+
81
+ function calculateAdvancedStats(trades) {
82
+ let totalProfit = 0, totalLoss = 0, wins = 0;
83
+ trades.forEach(trade => {
84
+ const pl = trade.income || 0;
85
+ if (pl > 0) { totalProfit += pl; wins++; } else { totalLoss += Math.abs(pl); }
86
+ });
87
+ const profitFactor = totalLoss ? (totalProfit / totalLoss) : 0;
88
+ const winRate = trades.length ? (wins / trades.length * 100) : 0;
89
+ return { profitFactor: profitFactor.toFixed(2), winRate: winRate.toFixed(1) };
90
+ }
91
+
92
+ function calculatePortfolioAllocation(positions) {
93
+ const totalValue = positions.reduce((sum, pos) => sum + (pos.positionValue || 0), 0);
94
+ const bySymbol = positions.reduce((acc, pos) => {
95
+ acc[pos.symbol] = (acc[pos.symbol] || 0) + (pos.positionValue || 0);
96
+ return acc;
97
+ }, {});
98
+ return { labels: Object.keys(bySymbol), data: Object.values(bySymbol).map(val => totalValue ? (val / totalValue * 100) : 0) };
99
+ }
100
+
101
+ // Update UI Functions
102
+ function updateTradingTable(positions, trades) {
103
+ const tbody = document.getElementById('trading-table-body');
104
+ tbody.innerHTML = '';
105
+ positions.forEach(pos => {
106
+ tbody.innerHTML += `
107
+ <tr class="border-b border-gray-200 dark:border-gray-700">
108
+ <td class="py-4">${pos.symbol}</td>
109
+ <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>
110
+ <td class="py-4">${pos.quantity}</td>
111
+ <td class="py-4">$${pos.entryPrice.toFixed(2)}</td>
112
+ <td class="py-4 font-medium">$${pos.markPrice.toFixed(2)}</td>
113
+ <td class="py-4 font-bold ${pos.unrealizedProfit > 0 ? 'text-green-500' : 'text-red-500'}">$${pos.unrealizedProfit.toFixed(2)}</td>
114
+ <td class="py-4"><span class="px-2 py-1 rounded bg-blue-100 text-blue-800">Open</span></td>
115
+ <td class="py-4 text-right"><button class="text-gray-400 hover:text-primary"><i class="fas fa-ellipsis-v"></i></button></td>
116
+ </tr>`;
117
+ });
118
+ trades.slice(0, 5).forEach(trade => {
119
+ tbody.innerHTML += `
120
+ <tr class="border-b border-gray-200 dark:border-gray-700">
121
+ <td class="py-4">${trade.symbol}</td>
122
+ <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>
123
+ <td class="py-4">${trade.quantity || 0}</td>
124
+ <td class="py-4">$${trade.entryPrice?.toFixed(2) || '0.00'}</td>
125
+ <td class="py-4 font-medium">$${trade.exitPrice?.toFixed(2) || '0.00'}</td>
126
+ <td class="py-4 font-bold ${trade.income > 0 ? 'text-green-500' : 'text-red-500'}">$${trade.income?.toFixed(2) || '0.00'}</td>
127
+ <td class="py-4"><span class="px-2 py-1 rounded bg-gray-100 text-gray-800">Closed</span></td>
128
+ <td class="py-4 text-right"><button class="text-gray-400 hover:text-primary"><i class="fas fa-ellipsis-v"></i></button></td>
129
+ </tr>`;
130
+ });
131
+ }
132
+
133
+ function updateAdvancedStats(stats) {
134
+ document.getElementById('advanced-stats').innerHTML = `
135
+ <div>
136
+ <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>
137
+ <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>
138
+ </div>
139
+ <div>
140
+ <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>
141
+ <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>
142
+ </div>`;
143
+ }
144
+
145
+ function updatePerformanceChart(trades) {
146
+ const monthlyPL = trades.reduce((acc, trade) => {
147
+ const month = new Date(trade.time).toLocaleString('default', { month: 'short' });
148
+ acc[month] = (acc[month] || 0) + (trade.income || 0);
149
+ return acc;
150
+ }, {});
151
+ const performanceChart = new Chart(document.getElementById('performanceChart').getContext('2d'), {
152
+ type: 'line',
153
+ data: { labels: Object.keys(monthlyPL), datasets: [{ label: 'Profit/Loss', data: Object.values(monthlyPL), borderColor: '#1E90FF', backgroundColor: 'rgba(30, 144, 255, 0.1)', tension: 0.4, fill: true }] },
154
+ 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 } } } }
155
+ });
156
+ }
157
+
158
+ function updateAllocationChart(allocation) {
159
+ const allocationChart = new Chart(document.getElementById('allocationChart').getContext('2d'), {
160
+ type: 'doughnut',
161
+ data: { labels: allocation.labels, datasets: [{ data: allocation.data, backgroundColor: ['#1E90FF', '#10b981', '#8b5cf6', '#f59e0b'], borderWidth: 0 }] },
162
+ options: { responsive: true, cutout: '65%', plugins: { legend: { display: false }, tooltip: { callbacks: { label: context => `${context.label}: ${context.parsed}%` } } } }
163
+ });
164
+ const legend = document.getElementById('allocation-legend');
165
+ legend.innerHTML = allocation.labels.map((label, i) => `
166
+ <div class="mb-3">
167
+ <div class="flex justify-between mb-1">
168
+ <span class="text-gray-500 dark:text-gray-400 flex items-center">
169
+ <span class="h-3 w-3 bg-[${allocationChart.data.datasets[0].backgroundColor[i]}] rounded-full mr-2"></span>
170
+ ${label}
171
+ </span>
172
+ <span class="font-medium dark:text-white">${allocation.data[i].toFixed(1)}%</span>
173
+ </div>
174
+ <div class="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700">
175
+ <div class="bg-[${allocationChart.data.datasets[0].backgroundColor[i]}] h-2 rounded-full" style="width: ${allocation.data[i]}%"></div>
176
+ </div>
177
+ </div>`).join('');
178
+ }
179
+
180
+ // Main Data Fetch Function
181
+ async function fetchData() {
182
+ try {
183
+ document.querySelectorAll('.trading-card h3').forEach(el => el.textContent = 'Loading...');
184
+ document.getElementById('trading-table-body').innerHTML = '<tr><td colspan="8" class="py-4 text-center">Loading...</td></tr>';
185
+
186
+ const [balance, positions, trades] = await Promise.all([
187
+ fetchBalance(),
188
+ fetchOpenPositions(),
189
+ fetchTradeHistory()
190
+ ]);
191
+
192
+ document.getElementById('total-balance').textContent = `$${balance.toFixed(2)}`;
193
+ document.getElementById('open-trades').textContent = positions.length;
194
+ const longCount = positions.filter(p => p.positionSide === 'LONG').length;
195
+ 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>`;
196
+ const todayProfit = calculateTodayProfit(trades);
197
+ document.getElementById('today-profit').textContent = `$${todayProfit.toFixed(2)}`;
198
+ const riskPercent = balance ? (positions.reduce((sum, p) => sum + (p.positionValue || 0), 0) / balance * 100) : 0;
199
+ document.getElementById('risk-exposure').textContent = riskPercent < 20 ? 'Low' : riskPercent < 50 ? 'Medium' : 'High';
200
+ document.getElementById('exposure-percent').innerHTML = `<span class="font-medium">${riskPercent.toFixed(1)}%</span><span class="ml-2">of balance</span>`;
201
+
202
+ updateTradingTable(positions, trades);
203
+ const stats = calculateAdvancedStats(trades);
204
+ updateAdvancedStats(stats);
205
+ updatePerformanceChart(trades);
206
+ const allocation = calculatePortfolioAllocation(positions);
207
+ updateAllocationChart(allocation);
208
+
209
+ document.getElementById('last-sync').textContent = `Last synced: ${new Date().toLocaleTimeString()}`;
210
+ document.getElementById('allocation-update').textContent = `Last updated: ${new Date().toLocaleTimeString()}`;
211
+ } catch (error) {
212
+ console.error('Error fetching data:', error);
213
+ alert(`Failed to sync with BingX API: ${error.message}. Please check your credentials in Hugging Face Space Secrets, permissions, network, or BingX API documentation (https://bingx-api.github.io/docs/#/en-us/swapV2/account-api.html).`);
214
+ }
215
+ }
216
+
217
+ // Event Listeners
218
+ document.getElementById('refresh-btn').addEventListener('click', fetchData);
219
+ document.getElementById('sync-now').addEventListener('click', fetchData);
220
+ window.addEventListener('load', fetchData);
221
+ setInterval(fetchData, 120000); // Refresh every 2 minutes, respecting 5/s limit
222
+
223
+ // Dark Mode Toggle
224
+ const themeToggle = document.getElementById('theme-toggle');
225
+ themeToggle.addEventListener('click', () => {
226
+ document.documentElement.classList.toggle('dark');
227
+ });
228
+
229
+ // Placeholder for Hot Dog Classifier (Static Space limitation)
230
+ document.getElementById('hotdog-section').innerHTML = `
231
+ <div class="bg-white dark:bg-darkCard rounded-2xl shadow-md p-6 mb-8">
232
+ <h3 class="text-lg font-bold text-gray-800 dark:text-white mb-4">Hot Dog Classifier</h3>
233
+ <p class="text-gray-500 dark:text-gray-400">This feature is not available in Static Spaces due to server-side model requirements. Use a Gradio Space or convert the model to TensorFlow.js for client-side inference.</p>
234
+ <p class="text-gray-500 dark:text-gray-400 mt-2">For TensorFlow.js setup, convert the 'julien-c/hotdog-not-hotdog' model using TensorFlow's model conversion tools and load it with <code>&lt;script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"&gt;&lt;/script&gt;</code>.</p>
235
+ </div>
236
+ `;
237
  </script>
238
  <style>
239
  .trading-card:hover { transform: translateY(-5px); box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); }
 
246
  </style>
247
  </head>
248
  <body class="bg-gray-100 dark:bg-darkBg transition-colors duration-300">
 
249
  <header class="bg-white dark:bg-darkCard shadow-sm">
250
  <div class="container mx-auto px-4 py-4 flex justify-between items-center">
251
  <div class="flex items-center">
 
275
  </div>
276
  </div>
277
  </header>
 
 
278
  <main class="container mx-auto px-4 py-8">
 
279
  <div class="flex justify-between items-center mb-8">
280
  <div>
281
  <h2 class="text-3xl font-bold text-gray-800 dark:text-white">Trading Dashboard</h2>
 
290
  </button>
291
  </div>
292
  </div>
 
 
293
  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
294
  <div class="trading-card bg-white dark:bg-darkCard rounded-2xl shadow-md p-6 transition-all duration-300">
295
  <div class="flex justify-between">
 
355
  </div>
356
  </div>
357
  </div>
 
 
358
  <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
359
  <div class="lg:col-span-2 bg-white dark:bg-darkCard rounded-2xl shadow-md p-6">
360
  <div class="flex justify-between items-center mb-6">
 
375
  </div>
376
  </div>
377
  </div>
 
 
378
  <div class="bg-white dark:bg-darkCard rounded-2xl shadow-md p-6 mb-8">
379
  <div class="flex justify-between items-center mb-6">
380
  <h3 class="text-lg font-bold text-gray-800 dark:text-white">Trading Activity</h3>
 
404
  </table>
405
  </div>
406
  </div>
 
 
407
  <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
408
  <div class="bg-gradient-to-r from-primary to-secondary rounded-2xl p-6">
409
  <div class="flex items-center mb-4">
 
417
  <p id="last-sync" class="text-blue-100">Last synced: Just now</p>
418
  </div>
419
  </div>
 
420
  <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">
421
  <i class="fas fa-cog mr-2"></i> Manage API Credentials
422
  </button>
423
  <div id="api-form" class="api-form">
424
+ <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" value="${BINGX_API_KEY || ''}" readonly>
425
+ <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" value="${BINGX_API_SECRET || ''}" readonly>
426
+ <button id="save-api-btn" class="w-full py-2 bg-gray-400 text-white rounded-lg cursor-not-allowed" disabled>Save Credentials (Read-Only)</button>
427
+ <p class="text-blue-100 text-sm mt-2">Credentials are managed via Hugging Face Space Secrets.</p>
428
  </div>
429
  <button id="sync-now" class="mt-2 w-full py-3 bg-white rounded-xl text-primary font-bold flex items-center justify-center">
430
  <i class="fas fa-sync mr-2"></i> Sync Now
 
447
  </div>
448
  </div>
449
  </div>
450
+ <div id="hotdog-section"></div>
451
  </main>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
  </body>
453
  </html>