EricSam commited on
Commit
b00fdc8
Β·
verified Β·
1 Parent(s): 7c8c324

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +500 -7
app.py CHANGED
@@ -1,16 +1,20 @@
1
- from flask import Flask, request, jsonify
 
2
  import hmac
3
  import hashlib
4
  import requests
5
  import time
6
  from datetime import datetime
 
 
7
 
 
8
  app = Flask(__name__)
9
 
10
- # Configuration (to be moved to environment variables in production)
11
  API_BASE_URL = "https://open-api.bingx.com"
12
- API_KEY = "" # Replace with environment variable or secure storage
13
- API_SECRET = "" # Replace with environment variable or secure storage
14
 
15
  # Generate Signature
16
  def generate_signature(api_secret, params_str):
@@ -23,7 +27,7 @@ def parse_params(params):
23
  params_str = "&".join(param_pairs)
24
  return params_str + "&timestamp=" + str(int(time.time() * 1000)) if params_str else "timestamp=" + str(int(time.time() * 1000))
25
 
26
- # Proxy Endpoint for API Requests
27
  @app.route('/api/proxy', methods=['GET'])
28
  def proxy_api():
29
  try:
@@ -49,5 +53,494 @@ def proxy_api():
49
  except Exception as e:
50
  return jsonify({"error": str(e)}), 500
51
 
52
- if __name__ == '__main__':
53
- app.run(host='0.0.0.0', port=7860, debug=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from transformers import pipeline
3
  import hmac
4
  import hashlib
5
  import requests
6
  import time
7
  from datetime import datetime
8
+ import os
9
+ from flask import Flask, request, jsonify
10
 
11
+ # Initialize Flask app for proxy (embedded within Gradio)
12
  app = Flask(__name__)
13
 
14
+ # Configuration (use environment variables for security)
15
  API_BASE_URL = "https://open-api.bingx.com"
16
+ API_KEY = os.getenv('BINGX_API_KEY', "") # Replace with environment variable or secure storage
17
+ API_SECRET = os.getenv('BINGX_API_SECRET', "") # Replace with environment variable or secure storage
18
 
19
  # Generate Signature
20
  def generate_signature(api_secret, params_str):
 
27
  params_str = "&".join(param_pairs)
28
  return params_str + "&timestamp=" + str(int(time.time() * 1000)) if params_str else "timestamp=" + str(int(time.time() * 1000))
29
 
30
+ # Proxy Endpoint for BingX API
31
  @app.route('/api/proxy', methods=['GET'])
32
  def proxy_api():
33
  try:
 
53
  except Exception as e:
54
  return jsonify({"error": str(e)}), 500
55
 
56
+ # Gradio Image Classification Setup
57
+ pipeline = pipeline(task="image-classification", model="julien-c/hotdog-not-hotdog")
58
+
59
+ def predict(input_img):
60
+ predictions = pipeline(input_img)
61
+ return input_img, {p["label"]: p["score"] for p in predictions}
62
+
63
+ # Gradio Interface
64
+ gradio_app = gr.Interface(
65
+ predict,
66
+ inputs=gr.Image(label="Select hot dog candidate", sources=['upload', 'webcam'], type="pil"),
67
+ outputs=[gr.Image(label="Processed Image"), gr.Label(label="Result", num_top_classes=2)],
68
+ title="Hot Dog? Or Not?",
69
+ )
70
+
71
+ # Custom JavaScript for BingX Dashboard
72
+ bingx_html = """
73
+ <!DOCTYPE html>
74
+ <html lang="en">
75
+ <head>
76
+ <meta charset="UTF-8">
77
+ <meta name="viewport" content="width=1024">
78
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
79
+ <title>Nakhoda4X Pro - Portfolio Dashboard</title>
80
+ <script src="https://cdn.tailwindcss.com"></script>
81
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
82
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
83
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
84
+ <script>
85
+ tailwind.config = {
86
+ darkMode: 'class',
87
+ theme: {
88
+ extend: {
89
+ colors: {
90
+ primary: '#1E90FF',
91
+ secondary: '#8b5cf6',
92
+ darkBg: '#111827',
93
+ darkCard: '#1f2937',
94
+ }
95
+ }
96
+ }
97
+ }
98
+ </script>
99
+ <style>
100
+ .trading-card:hover { transform: translateY(-5px); box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); }
101
+ .animate-pulse { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
102
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
103
+ .coin-animation { animation: float 3s ease-in-out infinite; }
104
+ @keyframes float { 0% { transform: translateY(0px); } 50% { transform: translateY(-10px); } 100% { transform: translateY(0px); } }
105
+ .api-form { max-height: 0; overflow: hidden; transition: max-height 0.3s ease-in-out; }
106
+ .api-form.open { max-height: 200px; }
107
+ </style>
108
+ </head>
109
+ <body class="bg-gray-100 dark:bg-darkBg transition-colors duration-300">
110
+ <header class="bg-white dark:bg-darkCard shadow-sm">
111
+ <div class="container mx-auto px-4 py-4 flex justify-between items-center">
112
+ <div class="flex items-center">
113
+ <div class="bg-primary w-10 h-10 rounded-xl flex items-center justify-center mr-3 coin-animation">
114
+ <i class="fas fa-chart-line text-white text-xl"></i>
115
+ </div>
116
+ <h1 class="text-2xl font-bold text-gray-800 dark:text-white">Nakhoda4X <span class="text-primary">Pro</span></h1>
117
+ </div>
118
+ <div class="flex items-center space-x-6">
119
+ <button id="theme-toggle" class="text-gray-600 dark:text-gray-300">
120
+ <i class="fas fa-moon dark:hidden"></i>
121
+ <i class="fas fa-sun hidden dark:block"></i>
122
+ </button>
123
+ <div class="relative">
124
+ <i class="fas fa-bell text-gray-600 dark:text-gray-300 text-xl"></i>
125
+ <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>
126
+ </div>
127
+ <div class="flex items-center">
128
+ <div class="mr-3 text-right">
129
+ <p class="font-semibold text-gray-800 dark:text-white">SahabatPipHijau</p>
130
+ <p class="text-sm text-gray-500">Premium Account</p>
131
+ </div>
132
+ <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">
133
+ J
134
+ </div>
135
+ </div>
136
+ </div>
137
+ </div>
138
+ </header>
139
+ <main class="container mx-auto px-4 py-8">
140
+ <div class="flex justify-between items-center mb-8">
141
+ <div>
142
+ <h2 class="text-3xl font-bold text-gray-800 dark:text-white">Trading Dashboard</h2>
143
+ <p class="text-gray-500 mt-1 dark:text-gray-400">Connected to BingX via API</p>
144
+ </div>
145
+ <div class="flex space-x-4">
146
+ <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">
147
+ <i class="fas fa-sync mr-2"></i> Refresh
148
+ </button>
149
+ <button class="px-4 py-2 bg-primary text-white rounded-lg flex items-center">
150
+ <i class="fas fa-plus mr-2"></i> New Trade
151
+ </button>
152
+ </div>
153
+ </div>
154
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
155
+ <div class="trading-card bg-white dark:bg-darkCard rounded-2xl shadow-md p-6 transition-all duration-300">
156
+ <div class="flex justify-between">
157
+ <div>
158
+ <p class="text-gray-500 dark:text-gray-400">Total Balance</p>
159
+ <h3 id="total-balance" class="text-2xl font-bold mt-2 dark:text-white">$0.00</h3>
160
+ </div>
161
+ <div class="w-12 h-12 rounded-xl bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center">
162
+ <i class="fas fa-wallet text-blue-500 text-xl"></i>
163
+ </div>
164
+ </div>
165
+ <div id="balance-change" class="mt-4 flex items-center text-green-500">
166
+ <i class="fas fa-caret-up mr-1"></i>
167
+ <span class="font-medium">0.0%</span>
168
+ <span class="text-gray-500 ml-2 dark:text-gray-400">last 24h</span>
169
+ </div>
170
+ </div>
171
+ <div class="trading-card bg-white dark:bg-darkCard rounded-2xl shadow-md p-6 transition-all duration-300">
172
+ <div class="flex justify-between">
173
+ <div>
174
+ <p class="text-gray-500 dark:text-gray-400">Open Trades</p>
175
+ <h3 id="open-trades" class="text-2xl font-bold mt-2 dark:text-white">0</h3>
176
+ </div>
177
+ <div class="w-12 h-12 rounded-xl bg-purple-100 dark:bg-purple-900/30 flex items-center justify-center">
178
+ <i class="fas fa-exchange-alt text-purple-500 text-xl"></i>
179
+ </div>
180
+ </div>
181
+ <div id="trade-types" class="mt-4 flex items-center text-blue-500">
182
+ <span class="font-medium">0 Long</span>
183
+ <span class="text-gray-500 mx-2 dark:text-gray-400">β€’</span>
184
+ <span class="font-medium">0 Short</span>
185
+ </div>
186
+ </div>
187
+ <div class="trading-card bg-white dark:bg-darkCard rounded-2xl shadow-md p-6 transition-all duration-300">
188
+ <div class="flex justify-between">
189
+ <div>
190
+ <p class="text-gray-500 dark:text-gray-400">Today's Profit</p>
191
+ <h3 id="today-profit" class="text-2xl font-bold mt-2 dark:text-white">$0.00</h3>
192
+ </div>
193
+ <div class="w-12 h-12 rounded-xl bg-green-100 dark:bg-green-900/30 flex items-center justify-center">
194
+ <i class="fas fa-chart-bar text-green-500 text-xl"></i>
195
+ </div>
196
+ </div>
197
+ <div id="profit-change" class="mt-4 flex items-center text-green-500">
198
+ <i class="fas fa-caret-up mr-1"></i>
199
+ <span class="font-medium">0.0%</span>
200
+ <span class="text-gray-500 ml-2 dark:text-gray-400">vs yesterday</span>
201
+ </div>
202
+ </div>
203
+ <div class="trading-card bg-white dark:bg-darkCard rounded-2xl shadow-md p-6 transition-all duration-300">
204
+ <div class="flex justify-between">
205
+ <div>
206
+ <p class="text-gray-500 dark:text-gray-400">Risk Exposure</p>
207
+ <h3 id="risk-exposure" class="text-2xl font-bold mt-2 dark:text-white">Low</h3>
208
+ </div>
209
+ <div class="w-12 h-12 rounded-xl bg-yellow-100 dark:bg-yellow-900/30 flex items-center justify-center">
210
+ <i class="fas fa-exclamation-triangle text-yellow-500 text-xl"></i>
211
+ </div>
212
+ </div>
213
+ <div id="exposure-percent" class="mt-4 flex items-center text-gray-500 dark:text-gray-400">
214
+ <span class="font-medium">0%</span>
215
+ <span class="ml-2">of balance</span>
216
+ </div>
217
+ </div>
218
+ </div>
219
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
220
+ <div class="lg:col-span-2 bg-white dark:bg-darkCard rounded-2xl shadow-md p-6">
221
+ <div class="flex justify-between items-center mb-6">
222
+ <h3 class="text-lg font-bold text-gray-800 dark:text-white">Monthly Performance</h3>
223
+ <div class="flex space-x-3">
224
+ <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>
225
+ <button class="px-3 py-1 rounded-lg text-sm bg-primary text-white">3M</button>
226
+ <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>
227
+ <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>
228
+ </div>
229
+ </div>
230
+ <canvas id="performanceChart"></canvas>
231
+ </div>
232
+ <div class="bg-white dark:bg-darkCard rounded-2xl shadow-md p-6">
233
+ <h3 class="text-lg font-bold text-gray-800 dark:text-white mb-6">Advanced Statistics</h3>
234
+ <div id="advanced-stats" class="space-y-5">
235
+ <!-- Dynamically populated -->
236
+ </div>
237
+ </div>
238
+ </div>
239
+ <div class="bg-white dark:bg-darkCard rounded-2xl shadow-md p-6 mb-8">
240
+ <div class="flex justify-between items-center mb-6">
241
+ <h3 class="text-lg font-bold text-gray-800 dark:text-white">Trading Activity</h3>
242
+ <div class="flex space-x-3">
243
+ <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>
244
+ <button class="px-3 py-1 rounded-lg text-sm bg-primary text-white">Open</button>
245
+ <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>
246
+ </div>
247
+ </div>
248
+ <div class="overflow-x-auto">
249
+ <table class="w-full">
250
+ <thead>
251
+ <tr class="text-left text-gray-500 dark:text-gray-400 text-sm">
252
+ <th class="pb-4">Symbol</th>
253
+ <th class="pb-4">Type</th>
254
+ <th class="pb-4">Size</th>
255
+ <th class="pb-4">Entry Price</th>
256
+ <th class="pb-4">Current Price</th>
257
+ <th class="pb-4">Profit/Loss</th>
258
+ <th class="pb-4">Status</th>
259
+ <th class="pb-4"></th>
260
+ </tr>
261
+ </thead>
262
+ <tbody id="trading-table-body" class="text-sm">
263
+ <!-- Dynamically populated -->
264
+ </tbody>
265
+ </table>
266
+ </div>
267
+ </div>
268
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
269
+ <div class="bg-gradient-to-r from-primary to-secondary rounded-2xl p-6">
270
+ <div class="flex items-center mb-4">
271
+ <div class="mr-4">
272
+ <div class="h-12 w-12 rounded-xl bg-white/30 flex items-center justify-center">
273
+ <i class="fas fa-plug text-white text-xl"></i>
274
+ </div>
275
+ </div>
276
+ <div>
277
+ <h3 class="text-lg font-bold text-white">BingX API Connected</h3>
278
+ <p id="last-sync" class="text-blue-100">Last synced: Just now</p>
279
+ </div>
280
+ </div>
281
+ <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">
282
+ <i class="fas fa-cog mr-2"></i> Manage API Credentials
283
+ </button>
284
+ <div id="api-form" class="api-form">
285
+ <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">
286
+ <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">
287
+ <button id="save-api-btn" class="w-full py-2 bg-primary text-white rounded-lg">Save Credentials</button>
288
+ </div>
289
+ <button id="sync-now" class="mt-2 w-full py-3 bg-white rounded-xl text-primary font-bold flex items-center justify-center">
290
+ <i class="fas fa-sync mr-2"></i> Sync Now
291
+ </button>
292
+ </div>
293
+ <div class="lg:col-span-2 bg-white dark:bg-darkCard rounded-2xl shadow-md p-6">
294
+ <div class="flex justify-between items-center mb-6">
295
+ <h3 class="text-lg font-bold text-gray-800 dark:text-white">Portfolio Allocation</h3>
296
+ <div>
297
+ <span id="allocation-update" class="text-gray-500 dark:text-gray-400 text-sm">Last updated: Just now</span>
298
+ </div>
299
+ </div>
300
+ <div class="flex items-center">
301
+ <div class="w-40 h-40 mr-8">
302
+ <canvas id="allocationChart"></canvas>
303
+ </div>
304
+ <div id="allocation-legend" class="flex-grow">
305
+ <!-- Dynamically populated -->
306
+ </div>
307
+ </div>
308
+ </div>
309
+ </div>
310
+ </main>
311
+ <script>
312
+ // Dark Mode Toggle
313
+ const themeToggle = document.getElementById('theme-toggle');
314
+ themeToggle.addEventListener('click', () => {
315
+ document.documentElement.classList.toggle('dark');
316
+ });
317
+
318
+ // Chart Initialization
319
+ const performanceCtx = document.getElementById('performanceChart').getContext('2d');
320
+ const performanceChart = new Chart(performanceCtx, {
321
+ type: 'line',
322
+ data: { labels: [], datasets: [{ label: 'Profit/Loss', data: [], borderColor: '#1E90FF', backgroundColor: 'rgba(30, 144, 255, 0.1)', tension: 0.4, fill: true }] },
323
+ 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 } } } }
324
+ });
325
+
326
+ const allocationCtx = document.getElementById('allocationChart').getContext('2d');
327
+ const allocationChart = new Chart(allocationCtx, {
328
+ type: 'doughnut',
329
+ data: { labels: [], datasets: [{ data: [], backgroundColor: ['#1E90FF', '#10b981', '#8b5cf6', '#f59e0b'], borderWidth: 0 }] },
330
+ options: { responsive: true, cutout: '65%', plugins: { legend: { display: false }, tooltip: { callbacks: { label: context => `${context.label}: ${context.parsed}%` } } } }
331
+ });
332
+
333
+ let API_KEY = localStorage.getItem('bingxApiKey') || '';
334
+ let API_SECRET = localStorage.getItem('bingxApiSecret') || '';
335
+ const PROXY_URL = '/api/proxy';
336
+
337
+ const toggleApiFormBtn = document.getElementById('toggle-api-form');
338
+ const apiForm = document.getElementById('api-form');
339
+ toggleApiFormBtn.addEventListener('click', () => {
340
+ apiForm.classList.toggle('open');
341
+ });
342
+
343
+ const saveApiBtn = document.getElementById('save-api-btn');
344
+ saveApiBtn.addEventListener('click', () => {
345
+ const newApiKey = document.getElementById('api-key-input').value.trim();
346
+ const newApiSecret = document.getElementById('api-secret-input').value.trim();
347
+ if (newApiKey && newApiSecret) {
348
+ API_KEY = newApiKey;
349
+ API_SECRET = newApiSecret;
350
+ localStorage.setItem('bingxApiKey', API_KEY);
351
+ localStorage.setItem('bingxApiSecret', API_SECRET);
352
+ alert('API credentials saved successfully! Syncing data...');
353
+ fetchData();
354
+ apiForm.classList.remove('open');
355
+ } else {
356
+ alert('Please enter both API Key and Secret.');
357
+ }
358
+ });
359
+
360
+ async function fetchFromProxy(endpoint, params = {}) {
361
+ if (!API_KEY || !API_SECRET) {
362
+ throw new Error('API Key and Secret are not set. Please enter your credentials.');
363
+ }
364
+ params.recvWindow = params.recvWindow || 5000;
365
+ const url = `${PROXY_URL}?endpoint=${encodeURIComponent(endpoint)}&${new URLSearchParams(params).toString()}`;
366
+ const response = await fetch(url);
367
+ if (!response.ok) {
368
+ const error = await response.json();
369
+ throw new Error(`API Error ${response.status}: ${error.error || 'Unknown error'}`);
370
+ }
371
+ return response.json();
372
+ }
373
+
374
+ async function fetchBalance() {
375
+ const data = await fetchFromProxy('/openApi/swap/v3/user/balance');
376
+ return data.data.find(b => b.asset === 'USDT')?.walletBalance || 0;
377
+ }
378
+
379
+ async function fetchOpenPositions() {
380
+ const data = await fetchFromProxy('/openApi/swap/v2/user/positions', { symbol: 'BTC-USDT' });
381
+ return data.data || [];
382
+ }
383
+
384
+ async function fetchTradeHistory() {
385
+ const data = await fetchFromProxy('/openApi/swap/v2/user/income', { limit: 100 });
386
+ return data.data || [];
387
+ }
388
+
389
+ function calculateTodayProfit(trades) {
390
+ const today = new Date().toDateString();
391
+ return trades.filter(trade => new Date(trade.time).toDateString() === today)
392
+ .reduce((sum, trade) => sum + (trade.income || 0), 0);
393
+ }
394
+
395
+ function calculateAdvancedStats(trades) {
396
+ let totalProfit = 0, totalLoss = 0, wins = 0;
397
+ trades.forEach(trade => {
398
+ const pl = trade.income || 0;
399
+ if (pl > 0) { totalProfit += pl; wins++; } else { totalLoss += Math.abs(pl); }
400
+ });
401
+ const profitFactor = totalLoss ? (totalProfit / totalLoss) : 0;
402
+ const winRate = trades.length ? (wins / trades.length * 100) : 0;
403
+ return { profitFactor: profitFactor.toFixed(2), winRate: winRate.toFixed(1) };
404
+ }
405
+
406
+ function calculatePortfolioAllocation(positions) {
407
+ const totalValue = positions.reduce((sum, pos) => sum + (pos.positionValue || 0), 0);
408
+ const bySymbol = positions.reduce((acc, pos) => {
409
+ acc[pos.symbol] = (acc[pos.symbol] || 0) + (pos.positionValue || 0);
410
+ return acc;
411
+ }, {});
412
+ return { labels: Object.keys(bySymbol), data: Object.values(bySymbol).map(val => totalValue ? (val / totalValue * 100) : 0) };
413
+ }
414
+
415
+ function updateTradingTable(positions, trades) {
416
+ const tbody = document.getElementById('trading-table-body');
417
+ tbody.innerHTML = '';
418
+ positions.forEach(pos => {
419
+ tbody.innerHTML += `
420
+ <tr class="border-b border-gray-200 dark:border-gray-700">
421
+ <td class="py-4">${pos.symbol}</td>
422
+ <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>
423
+ <td class="py-4">${pos.quantity}</td>
424
+ <td class="py-4">$${pos.entryPrice.toFixed(2)}</td>
425
+ <td class="py-4 font-medium">$${pos.markPrice.toFixed(2)}</td>
426
+ <td class="py-4 font-bold ${pos.unrealizedProfit > 0 ? 'text-green-500' : 'text-red-500'}">$${pos.unrealizedProfit.toFixed(2)}</td>
427
+ <td class="py-4"><span class="px-2 py-1 rounded bg-blue-100 text-blue-800">Open</span></td>
428
+ <td class="py-4 text-right"><button class="text-gray-400 hover:text-primary"><i class="fas fa-ellipsis-v"></i></button></td>
429
+ </tr>`;
430
+ });
431
+ trades.slice(0, 5).forEach(trade => {
432
+ tbody.innerHTML += `
433
+ <tr class="border-b border-gray-200 dark:border-gray-700">
434
+ <td class="py-4">${trade.symbol}</td>
435
+ <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>
436
+ <td class="py-4">${trade.quantity || 0}</td>
437
+ <td class="py-4">$${trade.entryPrice?.toFixed(2) || '0.00'}</td>
438
+ <td class="py-4 font-medium">$${trade.exitPrice?.toFixed(2) || '0.00'}</td>
439
+ <td class="py-4 font-bold ${trade.income > 0 ? 'text-green-500' : 'text-red-500'}">$${trade.income?.toFixed(2) || '0.00'}</td>
440
+ <td class="py-4"><span class="px-2 py-1 rounded bg-gray-100 text-gray-800">Closed</span></td>
441
+ <td class="py-4 text-right"><button class="text-gray-400 hover:text-primary"><i class="fas fa-ellipsis-v"></i></button></td>
442
+ </tr>`;
443
+ });
444
+ }
445
+
446
+ function updateAdvancedStats(stats) {
447
+ document.getElementById('advanced-stats').innerHTML = `
448
+ <div>
449
+ <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>
450
+ <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>
451
+ </div>
452
+ <div>
453
+ <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>
454
+ <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>
455
+ </div>`;
456
+ }
457
+
458
+ function updatePerformanceChart(trades) {
459
+ const monthlyPL = trades.reduce((acc, trade) => {
460
+ const month = new Date(trade.time).toLocaleString('default', { month: 'short' });
461
+ acc[month] = (acc[month] || 0) + (trade.income || 0);
462
+ return acc;
463
+ }, {});
464
+ performanceChart.data.labels = Object.keys(monthlyPL);
465
+ performanceChart.data.datasets[0].data = Object.values(monthlyPL);
466
+ performanceChart.update();
467
+ }
468
+
469
+ function updateAllocationChart(allocation) {
470
+ allocationChart.data.labels = allocation.labels;
471
+ allocationChart.data.datasets[0].data = allocation.data;
472
+ allocationChart.update();
473
+ const legend = document.getElementById('allocation-legend');
474
+ legend.innerHTML = allocation.labels.map((label, i) => `
475
+ <div class="mb-3">
476
+ <div class="flex justify-between mb-1">
477
+ <span class="text-gray-500 dark:text-gray-400 flex items-center">
478
+ <span class="h-3 w-3 bg-[${allocationChart.data.datasets[0].backgroundColor[i]}] rounded-full mr-2"></span>
479
+ ${label}
480
+ </span>
481
+ <span class="font-medium dark:text-white">${allocation.data[i].toFixed(1)}%</span>
482
+ </div>
483
+ <div class="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700">
484
+ <div class="bg-[${allocationChart.data.datasets[0].backgroundColor[i]}] h-2 rounded-full" style="width: ${allocation.data[i]}%"></div>
485
+ </div>
486
+ </div>`).join('');
487
+ }
488
+
489
+ async function fetchData() {
490
+ try {
491
+ document.querySelectorAll('.trading-card h3').forEach(el => el.textContent = 'Loading...');
492
+ document.getElementById('trading-table-body').innerHTML = '<tr><td colspan="8" class="py-4 text-center">Loading...</td></tr>';
493
+
494
+ if (!API_KEY || !API_SECRET) {
495
+ throw new Error('Please enter your BingX API Key and Secret in the credentials form.');
496
+ }
497
+
498
+ const [balance, positions, trades] = await Promise.all([
499
+ fetchBalance(),
500
+ fetchOpenPositions(),
501
+ fetchTradeHistory()
502
+ ]);
503
+
504
+ document.getElementById('total-balance').textContent = `$${balance.toFixed(2)}`;
505
+ document.getElementById('open-trades').textContent = positions.length;
506
+ const longCount = positions.filter(p => p.positionSide === 'LONG').length;
507
+ 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>`;
508
+ const todayProfit = calculateTodayProfit(trades);
509
+ document.getElementById('today-profit').textContent = `$${todayProfit.toFixed(2)}`;
510
+ const riskPercent = balance ? (positions.reduce((sum, p) => sum + (p.positionValue || 0), 0) / balance * 100) : 0;
511
+ document.getElementById('risk-exposure').textContent = riskPercent < 20 ? 'Low' : riskPercent < 50 ? 'Medium' : 'High';
512
+ document.getElementById('exposure-percent').innerHTML = `<span class="font-medium">${riskPercent.toFixed(1)}%</span><span class="ml-2">of balance</span>`;
513
+
514
+ updateTradingTable(positions, trades);
515
+ const stats = calculateAdvancedStats(trades);
516
+ updateAdvancedStats(stats);
517
+ updatePerformanceChart(trades);
518
+ const allocation = calculatePortfolioAllocation(positions);
519
+ updateAllocationChart(allocation);
520
+
521
+ document.getElementById('last-sync').textContent = `Last synced: ${new Date().toLocaleTimeString()}`;
522
+ document.getElementById('allocation-update').textContent = `Last updated: ${new Date().toLocaleTimeString()}`;
523
+ } catch (error) {
524
+ console.error('Error fetching data:', error);
525
+ alert(`Failed to sync with BingX API: ${error.message}. Please check your credentials, permissions, network, or BingX API documentation.`);
526
+ }
527
+ }
528
+
529
+ document.getElementById('refresh-btn').addEventListener('click', fetchData);
530
+ document.getElementById('sync-now').addEventListener('click', fetchData);
531
+ window.addEventListener('load', fetchData);
532
+ setInterval(fetchData, 120000);
533
+ </script>
534
+ </body>
535
+ </html>
536
+ """
537
+
538
+ # Combine Gradio with Custom HTML
539
+ demo = gr.TabbedInterface(
540
+ [gradio_app, gr.HTML(bingx_html)],
541
+ ["Hot Dog Classifier", "BingX Dashboard"],
542
+ title="Multi-Function App"
543
+ )
544
+
545
+ if __name__ == "__main__":
546
+ demo.launch()