Spaces:
Runtime error
Runtime error
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,290 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from transformers import pipeline
|
3 |
+
import hmac
|
4 |
+
import hashlib
|
5 |
+
import requests
|
6 |
+
import time
|
7 |
+
import os
|
8 |
+
from datetime import datetime
|
9 |
+
|
10 |
+
# Initialize hotdog classification pipeline
|
11 |
+
hotdog_pipeline = pipeline(task="image-classification", model="julien-c/hotdog-not-hotdog")
|
12 |
+
|
13 |
+
# Configuration
|
14 |
+
API_BASE_URL = "https://open-api.bingx.com"
|
15 |
+
API_KEY = os.getenv('BINGX_API_KEY', "")
|
16 |
+
API_SECRET = os.getenv('BINGX_API_SECRET', "")
|
17 |
+
|
18 |
+
# Generate Signature
|
19 |
+
def generate_signature(api_secret, params_str):
|
20 |
+
return hmac.new(api_secret.encode("utf-8"), params_str.encode("utf-8"), hashlib.sha256).hexdigest()
|
21 |
+
|
22 |
+
# Parse Parameters
|
23 |
+
def parse_params(params):
|
24 |
+
sorted_keys = sorted(params.keys())
|
25 |
+
param_pairs = [f"{key}={params[key]}" for key in sorted_keys]
|
26 |
+
params_str = "&".join(param_pairs)
|
27 |
+
return params_str + "×tamp=" + str(int(time.time() * 1000)) if params_str else "timestamp=" + str(int(time.time() * 1000))
|
28 |
+
|
29 |
+
# Fetch from BingX API
|
30 |
+
def fetch_from_api(endpoint, params=None):
|
31 |
+
if not API_KEY or not API_SECRET:
|
32 |
+
raise ValueError("BingX API Key and Secret are not set. Please configure them in Hugging Face Space Secrets.")
|
33 |
+
params = params or {}
|
34 |
+
params['recvWindow'] = params.get('recvWindow', '5000')
|
35 |
+
params_str = parse_params(params)
|
36 |
+
signature = generate_signature(API_SECRET, params_str)
|
37 |
+
url = f"{API_BASE_URL}{endpoint}?{params_str}&signature={signature}"
|
38 |
+
try:
|
39 |
+
response = requests.get(url, headers={'X-BX-APIKEY': API_KEY, 'Content-Type': 'application/json'})
|
40 |
+
response.raise_for_status()
|
41 |
+
return response.json()
|
42 |
+
except requests.exceptions.RequestException as e:
|
43 |
+
raise Exception(f"API Error: {str(e)}")
|
44 |
+
|
45 |
+
# Fetch Specific Data
|
46 |
+
def fetch_balance():
|
47 |
+
data = fetch_from_api('/openApi/swap/v3/user/balance')
|
48 |
+
return data['data'][0]['walletBalance'] if data['data'] and data['data'][0]['asset'] == 'USDT' else 0
|
49 |
+
|
50 |
+
def fetch_open_positions():
|
51 |
+
data = fetch_from_api('/openApi/swap/v2/user/positions', {'symbol': 'BTC-USDT'})
|
52 |
+
return data['data'] or []
|
53 |
+
|
54 |
+
def fetch_trade_history():
|
55 |
+
data = fetch_from_api('/openApi/swap/v2/user/income', {'limit': 100})
|
56 |
+
return data['data'] or []
|
57 |
+
|
58 |
+
# Helper Functions
|
59 |
+
def calculate_today_profit(trades):
|
60 |
+
today = datetime.now().date()
|
61 |
+
return sum(trade['income'] for trade in trades if datetime.fromtimestamp(trade['time'] / 1000).date() == today)
|
62 |
+
|
63 |
+
def calculate_advanced_stats(trades):
|
64 |
+
total_profit, total_loss, wins = 0, 0, 0
|
65 |
+
for trade in trades:
|
66 |
+
pl = trade['income']
|
67 |
+
if pl > 0:
|
68 |
+
total_profit += pl
|
69 |
+
wins += 1
|
70 |
+
else:
|
71 |
+
total_loss += abs(pl)
|
72 |
+
profit_factor = total_loss and (total_profit / total_loss) or 0
|
73 |
+
win_rate = trades and (wins / len(trades) * 100) or 0
|
74 |
+
return {'profit_factor': profit_factor, 'win_rate': win_rate}
|
75 |
+
|
76 |
+
def calculate_portfolio_allocation(positions):
|
77 |
+
total_value = sum(pos['positionValue'] for pos in positions if 'positionValue' in pos)
|
78 |
+
by_symbol = {}
|
79 |
+
for pos in positions:
|
80 |
+
by_symbol[pos['symbol']] = by_symbol.get(pos['symbol'], 0) + (pos.get('positionValue', 0))
|
81 |
+
labels = list(by_symbol.keys())
|
82 |
+
data = [val / total_value * 100 if total_value else 0 for val in by_symbol.values()]
|
83 |
+
return {'labels': labels, 'data': data}
|
84 |
+
|
85 |
+
# Update UI Functions (as Gradio components)
|
86 |
+
def update_trading_table(positions, trades):
|
87 |
+
table_rows = []
|
88 |
+
for pos in positions:
|
89 |
+
table_rows.append(f"""
|
90 |
+
<tr class="border-b border-gray-200 dark:border-gray-700">
|
91 |
+
<td class="py-4">{pos['symbol']}</td>
|
92 |
+
<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>
|
93 |
+
<td class="py-4">{pos['quantity']}</td>
|
94 |
+
<td class="py-4">${pos['entryPrice']:.2f}</td>
|
95 |
+
<td class="py-4 font-medium">${pos['markPrice']:.2f}</td>
|
96 |
+
<td class="py-4 font-bold {('text-green-500' if pos['unrealizedProfit'] > 0 else 'text-red-500')}">${pos['unrealizedProfit']:.2f}</td>
|
97 |
+
<td class="py-4"><span class="px-2 py-1 rounded bg-blue-100 text-blue-800">Open</span></td>
|
98 |
+
<td class="py-4 text-right"><button class="text-gray-400 hover:text-primary"><i class="fas fa-ellipsis-v"></i></button></td>
|
99 |
+
</tr>
|
100 |
+
""")
|
101 |
+
for trade in trades[:5]:
|
102 |
+
table_rows.append(f"""
|
103 |
+
<tr class="border-b border-gray-200 dark:border-gray-700">
|
104 |
+
<td class="py-4">{trade['symbol']}</td>
|
105 |
+
<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>
|
106 |
+
<td class="py-4">{trade.get('quantity', 0)}</td>
|
107 |
+
<td class="py-4">${trade.get('entryPrice', 0):.2f}</td>
|
108 |
+
<td class="py-4 font-medium">${trade.get('exitPrice', 0):.2f}</td>
|
109 |
+
<td class="py-4 font-bold {('text-green-500' if trade['income'] > 0 else 'text-red-500')}">${trade['income']:.2f}</td>
|
110 |
+
<td class="py-4"><span class="px-2 py-1 rounded bg-gray-100 text-gray-800">Closed</span></td>
|
111 |
+
<td class="py-4 text-right"><button class="text-gray-400 hover:text-primary"><i class="fas fa-ellipsis-v"></i></button></td>
|
112 |
+
</tr>
|
113 |
+
""")
|
114 |
+
return gr.HTML("\n".join(table_rows))
|
115 |
+
|
116 |
+
def update_advanced_stats(stats):
|
117 |
+
return gr.HTML(f"""
|
118 |
+
<div class="space-y-5">
|
119 |
+
<div>
|
120 |
+
<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']}</span></div>
|
121 |
+
<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>
|
122 |
+
</div>
|
123 |
+
<div>
|
124 |
+
<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']}%</span></div>
|
125 |
+
<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>
|
126 |
+
</div>
|
127 |
+
</div>
|
128 |
+
""")
|
129 |
+
|
130 |
+
def update_performance_chart(trades):
|
131 |
+
monthly_pl = {}
|
132 |
+
for trade in trades:
|
133 |
+
month = datetime.fromtimestamp(trade['time'] / 1000).strftime('%b')
|
134 |
+
monthly_pl[month] = monthly_pl.get(month, 0) + trade['income']
|
135 |
+
chart_data = {
|
136 |
+
'labels': list(monthly_pl.keys()),
|
137 |
+
'datasets': [{'label': 'Profit/Loss', 'data': list(monthly_pl.values()), 'borderColor': '#1E90FF', 'backgroundColor': 'rgba(30, 144, 255, 0.1)', 'tension': 0.4, 'fill': True}]
|
138 |
+
}
|
139 |
+
return gr.Chart(value=chart_data, type="line", options={
|
140 |
+
'responsive': True,
|
141 |
+
'plugins': {'legend': {'display': False}},
|
142 |
+
'scales': {'y': {'grid': {'color': 'rgba(0, 0, 0, 0.05)', 'borderDash': [5]}, 'ticks': {'callback': lambda value: f'${value}'}}, 'x': {'grid': {'display': False}}}
|
143 |
+
})
|
144 |
+
|
145 |
+
def update_allocation_chart(allocation):
|
146 |
+
chart_data = {
|
147 |
+
'labels': allocation['labels'],
|
148 |
+
'datasets': [{'data': allocation['data'], 'backgroundColor': ['#1E90FF', '#10b981', '#8b5cf6', '#f59e0b'], 'borderWidth': 0}]
|
149 |
+
}
|
150 |
+
legend_html = "\n".join(f"""
|
151 |
+
<div class="mb-3">
|
152 |
+
<div class="flex justify-between mb-1">
|
153 |
+
<span class="text-gray-500 dark:text-gray-400 flex items-center">
|
154 |
+
<span class="h-3 w-3 bg-[{chart_data['datasets'][0]['backgroundColor'][i]}] rounded-full mr-2"></span>
|
155 |
+
{label}
|
156 |
+
</span>
|
157 |
+
<span class="font-medium dark:text-white">{data:.1f}%</span>
|
158 |
+
</div>
|
159 |
+
<div class="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700">
|
160 |
+
<div class="bg-[{chart_data['datasets'][0]['backgroundColor'][i]}] h-2 rounded-full" style="width: {data}%"></div>
|
161 |
+
</div>
|
162 |
+
</div>
|
163 |
+
""" for i, (label, data) in enumerate(zip(allocation['labels'], allocation['data'])))
|
164 |
+
return gr.Chart(value=chart_data, type="doughnut", options={
|
165 |
+
'responsive': True, 'cutout': '65%', 'plugins': {'legend': {'display': False}, 'tooltip': {'callbacks': {'label': lambda context: f"{context['label']}: {context['parsed']}%"}}}
|
166 |
+
}), gr.HTML(legend_html)
|
167 |
+
|
168 |
+
# Main Data Fetch and Update Function
|
169 |
+
def update_dashboard():
|
170 |
+
try:
|
171 |
+
balance = fetch_balance()
|
172 |
+
positions = fetch_open_positions()
|
173 |
+
trades = fetch_trade_history()
|
174 |
+
|
175 |
+
total_balance = f"${balance:.2f}"
|
176 |
+
open_trades = len(positions)
|
177 |
+
long_count = len([p for p in positions if p['positionSide'] == 'LONG'])
|
178 |
+
today_profit = f"${calculate_today_profit(trades):.2f}"
|
179 |
+
risk_percent = balance and (sum(p.get('positionValue', 0) for p in positions) / balance * 100) or 0
|
180 |
+
risk_exposure = 'Low' if risk_percent < 20 else 'Medium' if risk_percent < 50 else 'High'
|
181 |
+
exposure_percent = f"{risk_percent:.1f}%"
|
182 |
+
|
183 |
+
stats = calculate_advanced_stats(trades)
|
184 |
+
allocation = calculate_portfolio_allocation(positions)
|
185 |
+
|
186 |
+
return (
|
187 |
+
total_balance, open_trades, f"{long_count} Long β’ {len(positions) - long_count} Short", today_profit,
|
188 |
+
risk_exposure, exposure_percent, update_trading_table(positions, trades), update_advanced_stats(stats),
|
189 |
+
update_performance_chart(trades), update_allocation_chart(allocation), datetime.now().strftime('%I:%M %p'),
|
190 |
+
datetime.now().strftime('%I:%M %p')
|
191 |
+
)
|
192 |
+
except Exception as e:
|
193 |
+
return (
|
194 |
+
"$0.00", 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</td></tr>"),
|
195 |
+
gr.HTML("<div>Error loading stats</div>"), gr.Chart(), (gr.Chart(), gr.HTML("")), "Just now", "Just now"
|
196 |
+
)
|
197 |
+
|
198 |
+
# Gradio Interface
|
199 |
+
with gr.Blocks(title="Nakhoda4X Pro") as demo:
|
200 |
+
gr.Markdown("""
|
201 |
+
# Nakhoda4X Pro
|
202 |
+
**Trading Dashboard** connected to BingX via API | **Hot Dog Classifier** for image analysis
|
203 |
+
""")
|
204 |
+
|
205 |
+
with gr.Tab("Trading Dashboard"):
|
206 |
+
with gr.Row():
|
207 |
+
with gr.Column():
|
208 |
+
gr.Markdown("### Trading Dashboard")
|
209 |
+
gr.Markdown("Connected to BingX via API")
|
210 |
+
with gr.Column():
|
211 |
+
refresh_btn = gr.Button("Refresh", variant="secondary")
|
212 |
+
new_trade_btn = gr.Button("New Trade", variant="primary")
|
213 |
+
|
214 |
+
with gr.Row():
|
215 |
+
with gr.Column(scale=1):
|
216 |
+
stats_cards = gr.Blocks()
|
217 |
+
with stats_cards:
|
218 |
+
with gr.Row():
|
219 |
+
gr.HTML("<div class='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6'>")
|
220 |
+
total_balance = gr.HTML("<h3 id='total-balance' class='text-2xl font-bold mt-2 dark:text-white'>$0.00</h3>")
|
221 |
+
open_trades = gr.HTML("<h3 id='open-trades' class='text-2xl font-bold mt-2 dark:text-white'>0</h3>")
|
222 |
+
today_profit = gr.HTML("<h3 id='today-profit' class='text-2xl font-bold mt-2 dark:text-white'>$0.00</h3>")
|
223 |
+
risk_exposure = gr.HTML("<h3 id='risk-exposure' class='text-2xl font-bold mt-2 dark:text-white'>Low</h3>")
|
224 |
+
with gr.Row():
|
225 |
+
gr.HTML("</div>")
|
226 |
+
balance_change = gr.HTML("<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>")
|
227 |
+
trade_types = gr.HTML("<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>")
|
228 |
+
profit_change = gr.HTML("<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>")
|
229 |
+
exposure_percent = gr.HTML("<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>")
|
230 |
+
|
231 |
+
with gr.Column(scale=3):
|
232 |
+
with gr.Row():
|
233 |
+
with gr.Column():
|
234 |
+
performance_chart = gr.Chart()
|
235 |
+
with gr.Column():
|
236 |
+
advanced_stats = gr.HTML()
|
237 |
+
|
238 |
+
with gr.Row():
|
239 |
+
trading_activity = gr.HTML()
|
240 |
+
|
241 |
+
with gr.Row():
|
242 |
+
with gr.Column():
|
243 |
+
api_connection = gr.Blocks()
|
244 |
+
with api_connection:
|
245 |
+
gr.HTML("<div class='bg-gradient-to-r from-primary to-secondary rounded-2xl p-6'>")
|
246 |
+
gr.HTML("<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>")
|
247 |
+
sync_now = gr.Button("Sync Now", elem_classes="w-full py-3 bg-white rounded-xl text-primary font-bold flex items-center justify-center")
|
248 |
+
with gr.Column():
|
249 |
+
portfolio_allocation = gr.Blocks()
|
250 |
+
with portfolio_allocation:
|
251 |
+
gr.HTML("<div class='bg-white dark:bg-darkCard rounded-2xl shadow-md p-6'>")
|
252 |
+
gr.HTML("<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>")
|
253 |
+
with gr.Row():
|
254 |
+
allocation_chart = gr.Chart()
|
255 |
+
allocation_legend = gr.HTML()
|
256 |
+
|
257 |
+
# Initial update
|
258 |
+
stats_cards.update(value=gr.HTML("<div class='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6'>"))
|
259 |
+
demo.load(fn=update_dashboard, inputs=None, outputs=[
|
260 |
+
total_balance, open_trades, trade_types, today_profit,
|
261 |
+
risk_exposure, exposure_percent, trading_activity, advanced_stats,
|
262 |
+
performance_chart, (allocation_chart, allocation_legend), gr.State(value=None), gr.State(value=None)
|
263 |
+
])
|
264 |
+
refresh_btn.click(fn=update_dashboard, inputs=None, outputs=[
|
265 |
+
total_balance, open_trades, trade_types, today_profit,
|
266 |
+
risk_exposure, exposure_percent, trading_activity, advanced_stats,
|
267 |
+
performance_chart, (allocation_chart, allocation_legend), gr.State(value=None), gr.State(value=None)
|
268 |
+
])
|
269 |
+
sync_now.click(fn=update_dashboard, inputs=None, outputs=[
|
270 |
+
total_balance, open_trades, trade_types, today_profit,
|
271 |
+
risk_exposure, exposure_percent, trading_activity, advanced_stats,
|
272 |
+
performance_chart, (allocation_chart, allocation_legend), gr.State(value=None), gr.State(value=None)
|
273 |
+
])
|
274 |
+
|
275 |
+
with gr.Tab("Hot Dog Classifier"):
|
276 |
+
gr.Markdown("### Hot Dog? Or Not?")
|
277 |
+
with gr.Row():
|
278 |
+
input_img = gr.Image(label="Select hot dog candidate", sources=['upload', 'webcam'], type="pil")
|
279 |
+
output_img = gr.Image(label="Processed Image")
|
280 |
+
output_label = gr.Label(label="Result", num_top_classes=2)
|
281 |
+
submit_btn = gr.Button("Classify")
|
282 |
+
submit_btn.click(fn=lambda img: (img, {p["label"]: p["score"] for p in hotdog_pipeline(img)}), inputs=input_img, outputs=[output_img, output_label])
|
283 |
+
|
284 |
+
demo.load(None, _js="() => [document.body.classList.toggle('dark', window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)]")
|
285 |
+
with gr.Row():
|
286 |
+
theme_toggle = gr.Button("Toggle Theme", elem_classes="text-gray-600 dark:text-gray-300")
|
287 |
+
theme_toggle.click(None, _js="() => document.body.classList.toggle('dark')")
|
288 |
+
|
289 |
+
if __name__ == "__main__":
|
290 |
+
demo.launch()
|