EricSam commited on
Commit
8c11a49
Β·
verified Β·
1 Parent(s): b5e8aa6

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +258 -225
index.html CHANGED
@@ -10,230 +10,19 @@
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,6 +35,7 @@
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,7 +65,10 @@
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,6 +83,8 @@
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,6 +150,8 @@
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,6 +172,8 @@
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,6 +203,8 @@
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,14 +218,14 @@
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,7 +248,239 @@
447
  </div>
448
  </div>
449
  </div>
450
- <div id="hotdog-section"></div>
451
  </main>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
  </body>
453
  </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
+ 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
  </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
  </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
  </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
  </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
  </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
  </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
  <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
  </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>