EricSam commited on
Commit
598d35e
Β·
verified Β·
1 Parent(s): 1b65420

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +289 -1
app.py CHANGED
@@ -42,4 +42,292 @@ def fetch_from_api(endpoint, params=None, api_key=api_key, api_secret=api_secret
42
  signature = generate_signature(api_secret.value, params_str)
43
  url = f"{API_BASE_URL}{endpoint}?{params_str}&signature={signature}"
44
  try:
45
- response = requests.get(url, headers={'X-BX-APIKEY': api_key.value, 'Content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  signature = generate_signature(api_secret.value, params_str)
43
  url = f"{API_BASE_URL}{endpoint}?{params_str}&signature={signature}"
44
  try:
45
+ response = requests.get(url, headers={'X-BX-APIKEY': api_key.value, 'Content-Type': 'application/json'})
46
+ response.raise_for_status()
47
+ return response.json()
48
+ except requests.exceptions.RequestException as e:
49
+ raise Exception(f"API Error: {str(e)}")
50
+
51
+ # Fetch Specific Data
52
+ def fetch_balance(api_key=api_key, api_secret=api_secret):
53
+ data = fetch_from_api('/openApi/swap/v3/user/balance', api_key=api_key, api_secret=api_secret)
54
+ return data['data'][0]['walletBalance'] if data['data'] and data['data'][0]['asset'] == 'USDT' else 0
55
+
56
+ def fetch_open_positions(api_key=api_key, api_secret=api_secret):
57
+ data = fetch_from_api('/openApi/swap/v2/user/positions', {'symbol': 'BTC-USDT'}, api_key=api_key, api_secret=api_secret)
58
+ return data['data'] or []
59
+
60
+ def fetch_trade_history(api_key=api_key, api_secret=api_secret):
61
+ data = fetch_from_api('/openApi/swap/v2/user/income', {'limit': 100}, api_key=api_key, api_secret=api_secret)
62
+ return data['data'] or []
63
+
64
+ # Helper Functions
65
+ def calculate_today_profit(trades):
66
+ today = datetime.now().date()
67
+ return sum(trade['income'] for trade in trades if datetime.fromtimestamp(trade['time'] / 1000).date() == today)
68
+
69
+ def calculate_advanced_stats(trades):
70
+ total_profit, total_loss, wins = 0, 0, 0
71
+ for trade in trades:
72
+ pl = trade['income']
73
+ if pl > 0:
74
+ total_profit += pl
75
+ wins += 1
76
+ else:
77
+ total_loss += abs(pl)
78
+ profit_factor = total_loss and (total_profit / total_loss) or 0
79
+ win_rate = trades and (wins / len(trades) * 100) or 0
80
+ return {'profit_factor': profit_factor, 'win_rate': win_rate}
81
+
82
+ def calculate_portfolio_allocation(positions):
83
+ total_value = sum(pos['positionValue'] for pos in positions if 'positionValue' in pos)
84
+ by_symbol = {}
85
+ for pos in positions:
86
+ by_symbol[pos['symbol']] = by_symbol.get(pos['symbol'], 0) + (pos.get('positionValue', 0))
87
+ labels = list(by_symbol.keys())
88
+ data = [val / total_value * 100 if total_value else 0 for val in by_symbol.values()]
89
+ return {'labels': labels, 'data': data}
90
+
91
+ # Update UI Functions
92
+ def update_trading_table(positions, trades):
93
+ table_rows = []
94
+ for pos in positions:
95
+ table_rows.append(f"""
96
+ <tr class="border-b border-gray-200 dark:border-gray-700">
97
+ <td class="py-4">{pos['symbol']}</td>
98
+ <td class="py-4"><span class="{('bg-green-100 text-green-800' if pos['positionSide'] == 'LONG' else 'bg-red-100 text-red-800')} px-2 py-1 rounded">{pos['positionSide']}</span></td>
99
+ <td class="py-4">{pos['quantity']}</td>
100
+ <td class="py-4">${pos['entryPrice']:.2f}</td>
101
+ <td class="py-4 font-medium">${pos['markPrice']:.2f}</td>
102
+ <td class="py-4 font-bold {('text-green-500' if pos['unrealizedProfit'] > 0 else 'text-red-500')}">${pos['unrealizedProfit']:.2f}</td>
103
+ <td class="py-4"><span class="px-2 py-1 rounded bg-blue-100 text-blue-800">Open</span></td>
104
+ <td class="py-4 text-right"><button class="text-gray-400 hover:text-primary"><i class="fas fa-ellipsis-v"></i></button></td>
105
+ </tr>
106
+ """)
107
+ for trade in trades[:5]:
108
+ table_rows.append(f"""
109
+ <tr class="border-b border-gray-200 dark:border-gray-700">
110
+ <td class="py-4">{trade['symbol']}</td>
111
+ <td class="py-4"><span class="{('bg-green-100 text-green-800' if trade['positionSide'] == 'LONG' else 'bg-red-100 text-red-800')} px-2 py-1 rounded">{trade['positionSide']}</span></td>
112
+ <td class="py-4">{trade.get('quantity', 0)}</td>
113
+ <td class="py-4">${trade.get('entryPrice', 0):.2f}</td>
114
+ <td class="py-4 font-medium">${trade.get('exitPrice', 0):.2f}</td>
115
+ <td class="py-4 font-bold {('text-green-500' if trade['income'] > 0 else 'text-red-500')}">${trade['income']:.2f}</td>
116
+ <td class="py-4"><span class="px-2 py-1 rounded bg-gray-100 text-gray-800">Closed</span></td>
117
+ <td class="py-4 text-right"><button class="text-gray-400 hover:text-primary"><i class="fas fa-ellipsis-v"></i></button></td>
118
+ </tr>
119
+ """)
120
+ return gr.HTML("\n".join(table_rows))
121
+
122
+ def update_advanced_stats(stats):
123
+ return gr.HTML(f"""
124
+ <div class="space-y-5">
125
+ <div>
126
+ <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['profit_factor']:.2f}</span></div>
127
+ <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: {min(stats['profit_factor'] * 25, 100)}%"></div></div>
128
+ </div>
129
+ <div>
130
+ <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['win_rate']:.1f}%</span></div>
131
+ <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['win_rate']}%"></div></div>
132
+ </div>
133
+ </div>
134
+ """)
135
+
136
+ def update_performance_chart(trades):
137
+ monthly_pl = {}
138
+ for trade in trades:
139
+ month = datetime.fromtimestamp(trade['time'] / 1000).strftime('%b')
140
+ monthly_pl[month] = monthly_pl.get(month, 0) + trade['income']
141
+ chart_data = {
142
+ 'labels': list(monthly_pl.keys()),
143
+ 'datasets': [{'label': 'Profit/Loss', 'data': list(monthly_pl.values()), 'borderColor': '#1E90FF', 'backgroundColor': 'rgba(30, 144, 255, 0.1)', 'tension': 0.4, 'fill': True}]
144
+ }
145
+ return gr.Chart(value=chart_data, type="line", options={
146
+ 'responsive': True,
147
+ 'plugins': {'legend': {'display': False}},
148
+ 'scales': {'y': {'grid': {'color': 'rgba(0, 0, 0, 0.05)', 'borderDash': [5]}, 'ticks': {'callback': lambda value: f'${value}'}}, 'x': {'grid': {'display': False}}}
149
+ })
150
+
151
+ def update_allocation_chart(allocation):
152
+ chart_data = {
153
+ 'labels': allocation['labels'],
154
+ 'datasets': [{'data': allocation['data'], 'backgroundColor': ['#1E90FF', '#10b981', '#8b5cf6', '#f59e0b'], 'borderWidth': 0}]
155
+ }
156
+ legend_html = "\n".join(f"""
157
+ <div class="mb-3">
158
+ <div class="flex justify-between mb-1">
159
+ <span class="text-gray-500 dark:text-gray-400 flex items-center">
160
+ <span class="h-3 w-3 bg-[{chart_data['datasets'][0]['backgroundColor'][i]}] rounded-full mr-2"></span>
161
+ {label}
162
+ </span>
163
+ <span class="font-medium dark:text-white">{data:.1f}%</span>
164
+ </div>
165
+ <div class="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700">
166
+ <div class="bg-[{chart_data['datasets'][0]['backgroundColor'][i]}] h-2 rounded-full" style="width: {data}%"></div>
167
+ </div>
168
+ </div>
169
+ """ for i, (label, data) in enumerate(zip(allocation['labels'], allocation['data'])))
170
+ return gr.Chart(value=chart_data, type="doughnut", options={
171
+ 'responsive': True, 'cutout': '65%', 'plugins': {'legend': {'display': False}, 'tooltip': {'callbacks': {'label': lambda context: f"{context['label']}: {context['parsed']}%"}}}
172
+ }), gr.HTML(legend_html)
173
+
174
+ # Main Data Fetch and Update Function
175
+ def update_dashboard(api_key, api_secret):
176
+ try:
177
+ balance = fetch_balance()
178
+ positions = fetch_open_positions()
179
+ trades = fetch_trade_history()
180
+
181
+ total_balance = f"${balance:.2f}"
182
+ open_trades = len(positions)
183
+ long_count = len([p for p in positions if p['positionSide'] == 'LONG'])
184
+ today_profit = f"${calculate_today_profit(trades):.2f}"
185
+ risk_percent = balance and (sum(p.get('positionValue', 0) for p in positions) / balance * 100) or 0
186
+ risk_exposure = 'Low' if risk_percent < 20 else 'Medium' if risk_percent < 50 else 'High'
187
+ exposure_percent = f"{risk_percent:.1f}%"
188
+
189
+ stats = calculate_advanced_stats(trades)
190
+ allocation = calculate_portfolio_allocation(positions)
191
+
192
+ return (
193
+ total_balance, open_trades, f"{long_count} Long β€’ {len(positions) - long_count} Short", today_profit,
194
+ risk_exposure, exposure_percent, update_trading_table(positions, trades), update_advanced_stats(stats),
195
+ update_performance_chart(trades), update_allocation_chart(allocation), datetime.now().strftime('%I:%M %p'),
196
+ datetime.now().strftime('%I:%M %p')
197
+ )
198
+ except Exception as e:
199
+ return (
200
+ "$Loading...", 0, "0 Long β€’ 0 Short", "$0.00", "Low", "0%", gr.HTML("<tr><td colspan='8' class='py-4 text-center'>Error: Failed to sync with BingX API - Check credentials</td></tr>"),
201
+ gr.HTML("<div>Error loading stats</div>"), gr.Chart(), (gr.Chart(), gr.HTML("")), "Just now", "Just now"
202
+ )
203
+
204
+ # Save Credentials Function
205
+ def save_credentials(new_api_key, new_api_secret, api_key, api_secret):
206
+ if not new_api_key or not new_api_secret:
207
+ return gr.Warning("Both API Key and Secret are required."), api_key, api_secret
208
+ return gr.Info("Credentials saved successfully! Syncing data..."), gr.State(value=new_api_key), gr.State(value=new_api_secret)
209
+
210
+ # Toggle API Form Visibility
211
+ def toggle_api_form(show_form):
212
+ return gr.update(visible=show_form)
213
+
214
+ # Gradio Interface
215
+ with gr.Blocks(title="Nakhoda4X Pro") as demo:
216
+ # Custom CSS and JS for Tailwind and dark mode
217
+ demo.css = """
218
+ .trading-card:hover { transform: translateY(-5px); box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); }
219
+ .animate-pulse { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
220
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
221
+ .coin-animation { animation: float 3s ease-in-out infinite; }
222
+ @keyframes float { 0% { transform: translateY(0px); } 50% { transform: translateY(-10px); } 100% { transform: translateY(0px); } }
223
+ .api-form { max-height: 0; overflow: hidden; transition: max-height 0.3s ease-in-out; }
224
+ .api-form.open { max-height: 200px; }
225
+ body { background-color: #f3f4f6; }
226
+ body.dark { background-color: #111827; }
227
+ .dark .bg-white { background-color: #1f2937; }
228
+ .dark .text-gray-800 { color: #ffffff; }
229
+ .dark .text-gray-500 { color: #9ca3af; }
230
+ """
231
+ demo.js = """
232
+ () => [document.body.classList.toggle('dark', window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)]
233
+ """
234
+
235
+ # Header
236
+ with gr.Row():
237
+ with gr.Column(scale=1):
238
+ gr.HTML("""
239
+ <div class="bg-white dark:bg-darkCard shadow-sm flex items-center p-4">
240
+ <div class="bg-primary w-10 h-10 rounded-xl flex items-center justify-center mr-3 coin-animation">
241
+ <i class="fas fa-chart-line text-white text-xl"></i>
242
+ </div>
243
+ <h1 class="text-2xl font-bold text-gray-800 dark:text-white">Nakhoda4X <span class="text-primary">Pro</span></h1>
244
+ </div>
245
+ """)
246
+ with gr.Column(scale=1, elem_classes="flex justify-end items-center space-x-6"):
247
+ theme_toggle = gr.Button(value="Toggle Theme", elem_classes="text-gray-600 dark:text-gray-300")
248
+ gr.HTML('<div><i class="fas fa-bell text-gray-600 dark:text-gray-300 text-xl"></i><span class="absolute -top-2 -right-2 bg-red-500 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center">3</span></div>')
249
+ gr.HTML('<div class="flex items-center"><div class="mr-3 text-right"><p class="font-semibold text-gray-800 dark:text-white">SahabatPipHijau</p><p class="text-sm text-gray-500">Premium Account</p></div><div class="h-10 w-10 rounded-full bg-gradient-to-r from-primary to-secondary flex items-center justify-center text-white font-bold">J</div></div>')
250
+
251
+ # Main Content
252
+ with gr.Row():
253
+ with gr.Column():
254
+ gr.Markdown("### Trading Dashboard")
255
+ gr.Markdown("Connected to BingX via API")
256
+ with gr.Row():
257
+ refresh_btn = gr.Button("Refresh", elem_classes="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")
258
+ new_trade_btn = gr.Button("New Trade", elem_classes="px-4 py-2 bg-primary text-white rounded-lg flex items-center")
259
+
260
+ # Stats Cards
261
+ with gr.Row(elem_classes="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"):
262
+ total_balance = gr.HTML("<div class='trading-card bg-white dark:bg-darkCard rounded-2xl shadow-md p-6'><div class='flex justify-between'><div><p class='text-gray-500 dark:text-gray-400'>Total Balance</p><h3 id='total-balance' class='text-2xl font-bold mt-2 dark:text-white'>$0.00</h3></div><div class='w-12 h-12 rounded-xl bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center'><i class='fas fa-wallet text-blue-500 text-xl'></i></div></div><div id='balance-change' class='mt-4 flex items-center text-green-500'><i class='fas fa-caret-up mr-1'></i><span class='font-medium'>0.0%</span><span class='text-gray-500 ml-2 dark:text-gray-400'>last 24h</span></div></div>")
263
+ open_trades = gr.HTML("<div class='trading-card bg-white dark:bg-darkCard rounded-2xl shadow-md p-6'><div class='flex justify-between'><div><p class='text-gray-500 dark:text-gray-400'>Open Trades</p><h3 id='open-trades' class='text-2xl font-bold mt-2 dark:text-white'>0</h3></div><div class='w-12 h-12 rounded-xl bg-purple-100 dark:bg-purple-900/30 flex items-center justify-center'><i class='fas fa-exchange-alt text-purple-500 text-xl'></i></div></div><div id='trade-types' class='mt-4 flex items-center text-blue-500'><span class='font-medium'>0 Long</span><span class='text-gray-500 mx-2 dark:text-gray-400'>β€’</span><span class='font-medium'>0 Short</span></div></div>")
264
+ today_profit = gr.HTML("<div class='trading-card bg-white dark:bg-darkCard rounded-2xl shadow-md p-6'><div class='flex justify-between'><div><p class='text-gray-500 dark:text-gray-400'>Today's Profit</p><h3 id='today-profit' class='text-2xl font-bold mt-2 dark:text-white'>$0.00</h3></div><div class='w-12 h-12 rounded-xl bg-green-100 dark:bg-green-900/30 flex items-center justify-center'><i class='fas fa-chart-bar text-green-500 text-xl'></i></div></div><div id='profit-change' class='mt-4 flex items-center text-green-500'><i class='fas fa-caret-up mr-1'></i><span class='font-medium'>0.0%</span><span class='text-gray-500 ml-2 dark:text-gray-400'>vs yesterday</span></div></div>")
265
+ risk_exposure = gr.HTML("<div class='trading-card bg-white dark:bg-darkCard rounded-2xl shadow-md p-6'><div class='flex justify-between'><div><p class='text-gray-500 dark:text-gray-400'>Risk Exposure</p><h3 id='risk-exposure' class='text-2xl font-bold mt-2 dark:text-white'>Low</h3></div><div class='w-12 h-12 rounded-xl bg-yellow-100 dark:bg-yellow-900/30 flex items-center justify-center'><i class='fas fa-exclamation-triangle text-yellow-500 text-xl'></i></div></div><div id='exposure-percent' class='mt-4 flex items-center text-gray-500 dark:text-gray-400'><span class='font-medium'>0%</span><span class='ml-2'>of balance</span></div></div>")
266
+
267
+ # Charts and Statistics
268
+ with gr.Row(elem_classes="grid grid-cols-1 lg:grid-cols-3 gap-6"):
269
+ with gr.Column(scale=2):
270
+ performance_chart = gr.Chart()
271
+ gr.HTML("<div class='flex justify-between items-center mb-6'><h3 class='text-lg font-bold text-gray-800 dark:text-white'>Monthly Performance</h3><div class='flex space-x-3'><button class='px-3 py-1 rounded-lg text-sm bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-white'>1M</button><button class='px-3 py-1 rounded-lg text-sm bg-primary text-white'>3M</button><button class='px-3 py-1 rounded-lg text-sm bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-white'>6M</button><button class='px-3 py-1 rounded-lg text-sm bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-white'>1Y</button></div></div>")
272
+ with gr.Column(scale=1):
273
+ advanced_stats = gr.HTML()
274
+
275
+ # Trading Activity
276
+ trading_activity = gr.HTML()
277
+ gr.HTML("<div class='flex justify-between items-center mb-6'><h3 class='text-lg font-bold text-gray-800 dark:text-white'>Trading Activity</h3><div class='flex space-x-3'><button class='px-3 py-1 rounded-lg text-sm bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-white'>All</button><button class='px-3 py-1 rounded-lg text-sm bg-primary text-white'>Open</button><button class='px-3 py-1 rounded-lg text-sm bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-white'>Closed</button></div></div>")
278
+
279
+ # API Connection and Portfolio Allocation
280
+ with gr.Row(elem_classes="grid grid-cols-1 lg:grid-cols-3 gap-6"):
281
+ with gr.Column():
282
+ api_connection = gr.Blocks()
283
+ with api_connection:
284
+ gr.HTML("<div class='bg-gradient-to-r from-primary to-secondary rounded-2xl p-6'><div class='flex items-center mb-4'><div class='mr-4'><div class='h-12 w-12 rounded-xl bg-white/30 flex items-center justify-center'><i class='fas fa-plug text-white text-xl'></i></div></div><div><h3 class='text-lg font-bold text-white'>BingX API Connected</h3><p id='last-sync' class='text-blue-100'>Last synced: Just now</p></div></div>")
285
+ show_form = gr.State(False)
286
+ toggle_btn = gr.Button("Manage API Credentials", elem_classes="w-full py-2 bg-white rounded-xl text-primary font-bold flex items-center justify-center mb-2")
287
+ with gr.Column(elem_classes="api-form", visible=False) as api_form:
288
+ api_key_input = gr.Textbox(label="API Key", value=api_key.value, type="password")
289
+ api_secret_input = gr.Textbox(label="API Secret", value=api_secret.value, type="password")
290
+ save_btn = gr.Button("Save Credentials", elem_classes="w-full py-2 bg-primary text-white rounded-lg")
291
+ sync_now = gr.Button("Sync Now", elem_classes="mt-2 w-full py-3 bg-white rounded-xl text-primary font-bold flex items-center justify-center")
292
+
293
+ with gr.Column(scale=2):
294
+ portfolio_allocation = gr.Blocks()
295
+ with portfolio_allocation:
296
+ gr.HTML("<div class='bg-white dark:bg-darkCard rounded-2xl shadow-md p-6'><div class='flex justify-between items-center mb-6'><h3 class='text-lg font-bold text-gray-800 dark:text-white'>Portfolio Allocation</h3><div><span id='allocation-update' class='text-gray-500 dark:text-gray-400 text-sm'>Last updated: Just now</span></div></div>")
297
+ allocation_chart, allocation_legend = gr.Chart(), gr.HTML()
298
+
299
+ # Event Handlers
300
+ toggle_btn.click(fn=toggle_api_form, inputs=[show_form], outputs=[api_form], _js="() => !document.querySelector('.api-form').classList.contains('open')")
301
+ save_btn.click(fn=save_credentials, inputs=[api_key_input, api_secret_input, api_key, api_secret], outputs=[gr.Markdown(), api_key, api_secret])
302
+ demo.load(fn=update_dashboard, inputs=[api_key, api_secret], outputs=[
303
+ total_balance, open_trades, trade_types, today_profit,
304
+ risk_exposure, exposure_percent, trading_activity, advanced_stats,
305
+ performance_chart, (allocation_chart, allocation_legend), gr.State(value=None), gr.State(value=None)
306
+ ])
307
+ refresh_btn.click(fn=update_dashboard, inputs=[api_key, api_secret], outputs=[
308
+ total_balance, open_trades, trade_types, today_profit,
309
+ risk_exposure, exposure_percent, trading_activity, advanced_stats,
310
+ performance_chart, (allocation_chart, allocation_legend), gr.State(value=None), gr.State(value=None)
311
+ ])
312
+ sync_now.click(fn=update_dashboard, inputs=[api_key, api_secret], outputs=[
313
+ total_balance, open_trades, trade_types, today_profit,
314
+ risk_exposure, exposure_percent, trading_activity, advanced_stats,
315
+ performance_chart, (allocation_chart, allocation_legend), gr.State(value=None), gr.State(value=None)
316
+ ])
317
+ new_trade_btn.click(None, _js="() => alert('New Trade functionality not implemented yet.')")
318
+
319
+ # Hot Dog Classifier Tab
320
+ with gr.Tab("Hot Dog Classifier"):
321
+ gr.Markdown("### Hot Dog? Or Not?")
322
+ with gr.Row():
323
+ input_img = gr.Image(label="Select hot dog candidate", sources=['upload', 'webcam'], type="pil")
324
+ output_img = gr.Image(label="Processed Image")
325
+ output_label = gr.Label(label="Result", num_top_classes=2)
326
+ submit_btn = gr.Button("Classify")
327
+ submit_btn.click(fn=lambda img: (img, {p["label"]: p["score"] for p in hotdog_pipeline(img)}) if hotdog_pipeline else (None, {"Error": "Classifier unavailable"}), inputs=input_img, outputs=[output_img, output_label])
328
+
329
+ # Theme Toggle
330
+ theme_toggle.click(None, _js="() => document.body.classList.toggle('dark')")
331
+
332
+ if __name__ == "__main__":
333
+ demo.launch()