Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# app.py (
|
| 2 |
import os
|
| 3 |
import sys
|
| 4 |
import traceback
|
|
@@ -492,22 +492,97 @@ async def get_full_status():
|
|
| 492 |
}
|
| 493 |
|
| 494 |
# ==============================================================================
|
| 495 |
-
# 📊 [ 💡 GEM-ARCHITECT
|
| 496 |
# ==============================================================================
|
| 497 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 498 |
async def check_live_pnl_and_status():
|
| 499 |
"""
|
| 500 |
-
|
| 501 |
-
(إصلاح: يستخدم 8 خانات عشرية لعرض العملات الصغيرة مثل SHIB)
|
| 502 |
"""
|
| 503 |
-
global trade_manager, data_manager, sys_state
|
| 504 |
|
| 505 |
empty_watchlist = pd.DataFrame(columns=["عملات المراقبة"])
|
|
|
|
|
|
|
|
|
|
|
|
|
| 506 |
|
| 507 |
if not sys_state.ready:
|
| 508 |
-
return "النظام قيد التهيئة...", "...", "...", "...", "...", None, empty_watchlist
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 509 |
|
| 510 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 511 |
try:
|
| 512 |
status_text = sys_state.last_cycle_logs
|
| 513 |
trade_count = len(trade_manager.open_positions)
|
|
@@ -517,25 +592,20 @@ async def check_live_pnl_and_status():
|
|
| 517 |
**الصفقات المفتوحة:** {trade_count} |
|
| 518 |
**قائمة المراقبة:** {watch_count}
|
| 519 |
"""
|
| 520 |
-
|
| 521 |
watchlist_items = list(trade_manager.watchlist.keys())
|
| 522 |
watchlist_df = pd.DataFrame(watchlist_items, columns=["عملات المراقبة"])
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
return f"خطأ في جلب الحالة: {e}", "...", "...", "...", "...", None, empty_watchlist
|
| 526 |
|
| 527 |
-
#
|
| 528 |
if not trade_manager.open_positions:
|
| 529 |
-
return status_text, status_md, "### 💤 لا توجد صفقة مفتوحة", "---", "---", None, watchlist_df
|
| 530 |
|
| 531 |
try:
|
| 532 |
symbol = list(trade_manager.open_positions.keys())[0]
|
| 533 |
trade = trade_manager.open_positions[symbol]
|
| 534 |
|
| 535 |
-
# 3. [GEM-FIX] جلب السعر الحي ومعالجة الأصفار
|
| 536 |
current_price = await data_manager.get_latest_price_async(symbol)
|
| 537 |
-
|
| 538 |
-
# إذا فشل الجلب (0.0)، نستخدم سعر الدخول للعرض فقط
|
| 539 |
if current_price <= 0.0:
|
| 540 |
current_price = float(trade.get('entry_price', 0.0))
|
| 541 |
color = "gray"
|
|
@@ -543,22 +613,12 @@ async def check_live_pnl_and_status():
|
|
| 543 |
else:
|
| 544 |
entry_price = float(trade['entry_price'])
|
| 545 |
pnl_pct = ((current_price - entry_price) / entry_price) * 100
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
if pnl_pct > 0:
|
| 549 |
-
color = "#00ff00" # Green
|
| 550 |
-
pnl_text = f"ربح +{pnl_pct:.2f}% 🚀"
|
| 551 |
-
elif pnl_pct < 0:
|
| 552 |
-
color = "#ff0000" # Red
|
| 553 |
-
pnl_text = f"خسارة {pnl_pct:.2f}% 🔻"
|
| 554 |
-
else:
|
| 555 |
-
color = "white"
|
| 556 |
-
pnl_text = "تعادل 0.00%"
|
| 557 |
|
| 558 |
symbol_md = f"# {symbol}"
|
| 559 |
-
pnl_md = f"<div style='font-size:
|
| 560 |
|
| 561 |
-
# 5. [GEM-FIX] تنسيق الأرقام باستخدام الدالة الجديدة (Full Precision)
|
| 562 |
entry_fmt = format_crypto_price(trade['entry_price'])
|
| 563 |
curr_fmt = format_crypto_price(current_price)
|
| 564 |
tp_fmt = format_crypto_price(trade['tp_price'])
|
|
@@ -566,54 +626,44 @@ async def check_live_pnl_and_status():
|
|
| 566 |
|
| 567 |
details_md = f"""
|
| 568 |
<div style='background-color: #222; padding: 10px; border-radius: 5px;'>
|
| 569 |
-
<b>
|
| 570 |
-
<b>
|
| 571 |
<span style='color:#00ff00;'><b>TP:</b> {tp_fmt}</span><br>
|
| 572 |
<span style='color:#ff0000;'><b>SL:</b> {sl_fmt}</span>
|
| 573 |
</div>
|
| 574 |
"""
|
| 575 |
|
| 576 |
-
#
|
| 577 |
fig = None
|
| 578 |
try:
|
| 579 |
ohlcv = await data_manager.get_latest_ohlcv(symbol, '5m', 100)
|
| 580 |
if ohlcv and len(ohlcv) > 0:
|
| 581 |
df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
|
| 582 |
df['datetime'] = pd.to_datetime(df['timestamp'], unit='ms')
|
| 583 |
-
|
| 584 |
fig = plt.figure(figsize=(10, 5))
|
| 585 |
ax = fig.add_subplot(111)
|
| 586 |
-
|
| 587 |
fig.patch.set_facecolor('#0b0f19')
|
| 588 |
ax.set_facecolor('#0b0f19')
|
| 589 |
-
|
| 590 |
ax.plot(df['datetime'], df['close'], label=f'{symbol}', color='#00e5ff')
|
| 591 |
ax.axhline(y=float(trade['entry_price']), color='white', linestyle='--', label='Entry')
|
| 592 |
ax.axhline(y=float(trade['tp_price']), color='#00ff00', linestyle='-', label='TP')
|
| 593 |
ax.axhline(y=float(trade['sl_price']), color='#ff0000', linestyle='-', label='SL')
|
| 594 |
-
|
| 595 |
ax.tick_params(axis='x', colors='white')
|
| 596 |
ax.tick_params(axis='y', colors='white')
|
| 597 |
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
|
| 598 |
ax.legend(facecolor='#222', labelcolor='white')
|
| 599 |
-
|
| 600 |
plt.tight_layout()
|
| 601 |
else:
|
| 602 |
fig = None
|
| 603 |
-
|
| 604 |
-
except Exception as e:
|
| 605 |
-
print(f"❌ خطأ في رسم المخطط: {e}")
|
| 606 |
-
fig = None
|
| 607 |
|
| 608 |
-
return status_text, status_md, symbol_md, pnl_md, details_md, fig, watchlist_df
|
| 609 |
|
| 610 |
except Exception as e:
|
| 611 |
-
|
| 612 |
-
return status_text, status_md, f"خطأ: {e}", "...", "...", None, empty_watchlist
|
| 613 |
|
| 614 |
async def run_cycle_from_gradio():
|
| 615 |
-
if sys_state.cycle_running:
|
| 616 |
-
return "الدورة قيد التشغيل بالفعل. يرجى الانتظار."
|
| 617 |
await run_unified_cycle()
|
| 618 |
return sys_state.last_cycle_logs
|
| 619 |
|
|
@@ -621,58 +671,104 @@ async def run_cycle_from_gradio():
|
|
| 621 |
# 🚀 بناء وتثبيت واجهة Gradio
|
| 622 |
# ==============================================================================
|
| 623 |
def create_gradio_ui():
|
| 624 |
-
css = "
|
|
|
|
|
|
|
|
|
|
| 625 |
|
| 626 |
-
with gr.Blocks(title="
|
| 627 |
-
gr.Markdown("#
|
| 628 |
|
|
|
|
| 629 |
with gr.Row():
|
|
|
|
| 630 |
with gr.Column(scale=3):
|
| 631 |
-
live_chart = gr.Plot(label="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 632 |
with gr.Column(scale=1):
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
|
| 637 |
gr.HTML("<hr>")
|
| 638 |
|
|
|
|
| 639 |
with gr.Row():
|
| 640 |
with gr.Column(scale=1):
|
| 641 |
-
gr.Markdown("##
|
| 642 |
-
|
| 643 |
-
gr.
|
| 644 |
-
|
| 645 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 646 |
|
| 647 |
with gr.Column(scale=3):
|
| 648 |
-
gr.Markdown("## 📜 سجلات
|
| 649 |
cycle_logs_output = gr.Textbox(
|
| 650 |
-
|
| 651 |
-
lines=20,
|
| 652 |
autoscroll=True,
|
| 653 |
max_lines=100,
|
| 654 |
-
value="...
|
| 655 |
)
|
| 656 |
|
|
|
|
|
|
|
|
|
|
| 657 |
run_cycle_btn.click(
|
| 658 |
fn=run_cycle_from_gradio,
|
| 659 |
inputs=None,
|
| 660 |
-
outputs=cycle_logs_output
|
| 661 |
-
api_name="run_cycle_manual"
|
| 662 |
)
|
| 663 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 664 |
demo.load(
|
| 665 |
fn=check_live_pnl_and_status,
|
| 666 |
inputs=None,
|
| 667 |
-
outputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 668 |
)
|
| 669 |
|
| 670 |
-
timer = gr.Timer(
|
| 671 |
-
|
| 672 |
timer.tick(
|
| 673 |
fn=check_live_pnl_and_status,
|
| 674 |
inputs=None,
|
| 675 |
-
outputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 676 |
)
|
| 677 |
|
| 678 |
return demo
|
|
@@ -681,7 +777,7 @@ gradio_dashboard = create_gradio_ui()
|
|
| 681 |
app = gr.mount_gradio_app(app, gradio_dashboard, path="/")
|
| 682 |
|
| 683 |
# ==============================================================================
|
| 684 |
-
# 🏁 تشغيل الخادم
|
| 685 |
# ==============================================================================
|
| 686 |
if __name__ == "__main__":
|
| 687 |
import uvicorn
|
|
|
|
| 1 |
+
# app.py (V18.0 - GEM-Architect: Full Stack + UI Enhancements)
|
| 2 |
import os
|
| 3 |
import sys
|
| 4 |
import traceback
|
|
|
|
| 492 |
}
|
| 493 |
|
| 494 |
# ==============================================================================
|
| 495 |
+
# 📊 [ 💡 GEM-ARCHITECT: NEW UI LOGIC ]
|
| 496 |
# ==============================================================================
|
| 497 |
|
| 498 |
+
async def manual_close_current_trade():
|
| 499 |
+
"""
|
| 500 |
+
دالة زر الإغلاق اليدوي
|
| 501 |
+
"""
|
| 502 |
+
if not trade_manager.open_positions:
|
| 503 |
+
return "⚠️ لا توجد صفقة مفتوحة لإغلاقها."
|
| 504 |
+
|
| 505 |
+
symbol = list(trade_manager.open_positions.keys())[0]
|
| 506 |
+
await trade_manager.force_exit_by_manager(symbol, reason="MANUAL_UI_BUTTON")
|
| 507 |
+
return f"✅ تم إرسال أمر إغلاق فوري لـ {symbol}."
|
| 508 |
+
|
| 509 |
async def check_live_pnl_and_status():
|
| 510 |
"""
|
| 511 |
+
تحديث الواجهة الدورية (الرسوم، الحالة، الإحصائيات، المحفظة)
|
|
|
|
| 512 |
"""
|
| 513 |
+
global trade_manager, data_manager, sys_state, r2
|
| 514 |
|
| 515 |
empty_watchlist = pd.DataFrame(columns=["عملات المراقبة"])
|
| 516 |
+
|
| 517 |
+
# Default Values for Stats
|
| 518 |
+
wallet_md = "### 💰 جاري التحميل..."
|
| 519 |
+
history_md = "### 📊 جاري التحميل..."
|
| 520 |
|
| 521 |
if not sys_state.ready:
|
| 522 |
+
return "النظام قيد التهيئة...", "...", "...", "...", "...", None, empty_watchlist, wallet_md, history_md
|
| 523 |
+
|
| 524 |
+
# 1. جلب بيانات المحفظة والإحصائيات من R2
|
| 525 |
+
try:
|
| 526 |
+
portfolio_state = await r2.get_portfolio_state_async()
|
| 527 |
+
current_capital = portfolio_state.get('current_capital_usd', 0.0)
|
| 528 |
+
total_trades = portfolio_state.get('total_trades', 0)
|
| 529 |
+
winning_trades = portfolio_state.get('winning_trades', 0)
|
| 530 |
+
total_profit = portfolio_state.get('total_profit_usd', 0.0)
|
| 531 |
+
|
| 532 |
+
win_rate = (winning_trades / total_trades * 100) if total_trades > 0 else 0.0
|
| 533 |
+
|
| 534 |
+
# Dynamic Wallet Calculation
|
| 535 |
+
dynamic_balance = current_capital
|
| 536 |
+
pnl_unrealized = 0.0
|
| 537 |
+
|
| 538 |
+
if trade_manager.open_positions:
|
| 539 |
+
symbol = list(trade_manager.open_positions.keys())[0]
|
| 540 |
+
trade = trade_manager.open_positions[symbol]
|
| 541 |
+
entry_price = float(trade.get('entry_price', 0.0))
|
| 542 |
+
|
| 543 |
+
curr_price = await data_manager.get_latest_price_async(symbol)
|
| 544 |
+
if curr_price > 0 and entry_price > 0:
|
| 545 |
+
# نفترض استثمار كامل رأس المال (للمحاكاة) أو نستخدم logic آخر
|
| 546 |
+
# هنا سنحسب نسبة الربح ونضيفها كرصيد وهمي للعرض
|
| 547 |
+
pnl_pct = (curr_price - entry_price) / entry_price
|
| 548 |
+
pnl_unrealized = current_capital * pnl_pct
|
| 549 |
+
dynamic_balance = current_capital + pnl_unrealized
|
| 550 |
+
|
| 551 |
+
# تنسيق عرض المحفظة
|
| 552 |
+
wallet_color = "#00ff00" if pnl_unrealized >= 0 else "#ff0000"
|
| 553 |
+
wallet_md = f"""
|
| 554 |
+
<div style='background-color: #1a1a1a; padding: 15px; border-radius: 8px; border: 1px solid #333;'>
|
| 555 |
+
<h3 style='margin-top:0; color: #888;'>💰 المحفظة الحية</h3>
|
| 556 |
+
<div style='font-size: 24px; font-weight: bold; color: white;'>
|
| 557 |
+
${dynamic_balance:,.2f}
|
| 558 |
+
</div>
|
| 559 |
+
<div style='font-size: 14px; color: {wallet_color};'>
|
| 560 |
+
({pnl_unrealized:+\,.2f} USD غير محقق)
|
| 561 |
+
</div>
|
| 562 |
+
<div style='font-size: 12px; color: #666; margin-top:5px;'>
|
| 563 |
+
الرصيد الأساسي: ${current_capital:,.2f}
|
| 564 |
+
</div>
|
| 565 |
+
</div>
|
| 566 |
+
"""
|
| 567 |
|
| 568 |
+
# تنسيق عرض الإحصائيات
|
| 569 |
+
history_md = f"""
|
| 570 |
+
<div style='background-color: #1a1a1a; padding: 15px; border-radius: 8px; border: 1px solid #333;'>
|
| 571 |
+
<h3 style='margin-top:0; color: #888;'>📊 الأداء التاريخي</h3>
|
| 572 |
+
<table style='width:100%; color:white;'>
|
| 573 |
+
<tr><td>إجمالي الصفقات:</td><td style='text-align:left; font-weight:bold;'>{total_trades}</td></tr>
|
| 574 |
+
<tr><td>نسبة الفوز:</td><td style='text-align:left; font-weight:bold; color:#00ff00;'>{win_rate:.1f}%</td></tr>
|
| 575 |
+
<tr><td>الرابحة:</td><td style='text-align:left;'>{winning_trades}</td></tr>
|
| 576 |
+
<tr><td>صافي الربح:</td><td style='text-align:left; color: {"#00ff00" if total_profit>=0 else "#ff0000"};'>${total_profit:,.2f}</td></tr>
|
| 577 |
+
</table>
|
| 578 |
+
</div>
|
| 579 |
+
"""
|
| 580 |
+
|
| 581 |
+
except Exception as e:
|
| 582 |
+
wallet_md = f"Error: {e}"
|
| 583 |
+
history_md = "Error loading stats"
|
| 584 |
+
|
| 585 |
+
# 2. الحالة العامة
|
| 586 |
try:
|
| 587 |
status_text = sys_state.last_cycle_logs
|
| 588 |
trade_count = len(trade_manager.open_positions)
|
|
|
|
| 592 |
**الصفقات المفتوحة:** {trade_count} |
|
| 593 |
**قائمة المراقبة:** {watch_count}
|
| 594 |
"""
|
|
|
|
| 595 |
watchlist_items = list(trade_manager.watchlist.keys())
|
| 596 |
watchlist_df = pd.DataFrame(watchlist_items, columns=["عملات المراقبة"])
|
| 597 |
+
except Exception:
|
| 598 |
+
return "Error", "...", "...", "...", "...", None, empty_watchlist, wallet_md, history_md
|
|
|
|
| 599 |
|
| 600 |
+
# 3. التحقق من الصفقة المفتوحة (للشارت والـ PnL)
|
| 601 |
if not trade_manager.open_positions:
|
| 602 |
+
return status_text, status_md, "### 💤 لا توجد صفقة مفتوحة", "---", "---", None, watchlist_df, wallet_md, history_md
|
| 603 |
|
| 604 |
try:
|
| 605 |
symbol = list(trade_manager.open_positions.keys())[0]
|
| 606 |
trade = trade_manager.open_positions[symbol]
|
| 607 |
|
|
|
|
| 608 |
current_price = await data_manager.get_latest_price_async(symbol)
|
|
|
|
|
|
|
| 609 |
if current_price <= 0.0:
|
| 610 |
current_price = float(trade.get('entry_price', 0.0))
|
| 611 |
color = "gray"
|
|
|
|
| 613 |
else:
|
| 614 |
entry_price = float(trade['entry_price'])
|
| 615 |
pnl_pct = ((current_price - entry_price) / entry_price) * 100
|
| 616 |
+
color = "#00ff00" if pnl_pct > 0 else "#ff0000"
|
| 617 |
+
pnl_text = f"{pnl_pct:+.2f}%"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 618 |
|
| 619 |
symbol_md = f"# {symbol}"
|
| 620 |
+
pnl_md = f"<div style='font-size: 40px; color:{color}; text-align:center; font-weight:bold;'>{pnl_text}</div>"
|
| 621 |
|
|
|
|
| 622 |
entry_fmt = format_crypto_price(trade['entry_price'])
|
| 623 |
curr_fmt = format_crypto_price(current_price)
|
| 624 |
tp_fmt = format_crypto_price(trade['tp_price'])
|
|
|
|
| 626 |
|
| 627 |
details_md = f"""
|
| 628 |
<div style='background-color: #222; padding: 10px; border-radius: 5px;'>
|
| 629 |
+
<b>دخول:</b> {entry_fmt}<br>
|
| 630 |
+
<b>حالياً:</b> {curr_fmt}<br>
|
| 631 |
<span style='color:#00ff00;'><b>TP:</b> {tp_fmt}</span><br>
|
| 632 |
<span style='color:#ff0000;'><b>SL:</b> {sl_fmt}</span>
|
| 633 |
</div>
|
| 634 |
"""
|
| 635 |
|
| 636 |
+
# Chart
|
| 637 |
fig = None
|
| 638 |
try:
|
| 639 |
ohlcv = await data_manager.get_latest_ohlcv(symbol, '5m', 100)
|
| 640 |
if ohlcv and len(ohlcv) > 0:
|
| 641 |
df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
|
| 642 |
df['datetime'] = pd.to_datetime(df['timestamp'], unit='ms')
|
|
|
|
| 643 |
fig = plt.figure(figsize=(10, 5))
|
| 644 |
ax = fig.add_subplot(111)
|
|
|
|
| 645 |
fig.patch.set_facecolor('#0b0f19')
|
| 646 |
ax.set_facecolor('#0b0f19')
|
|
|
|
| 647 |
ax.plot(df['datetime'], df['close'], label=f'{symbol}', color='#00e5ff')
|
| 648 |
ax.axhline(y=float(trade['entry_price']), color='white', linestyle='--', label='Entry')
|
| 649 |
ax.axhline(y=float(trade['tp_price']), color='#00ff00', linestyle='-', label='TP')
|
| 650 |
ax.axhline(y=float(trade['sl_price']), color='#ff0000', linestyle='-', label='SL')
|
|
|
|
| 651 |
ax.tick_params(axis='x', colors='white')
|
| 652 |
ax.tick_params(axis='y', colors='white')
|
| 653 |
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
|
| 654 |
ax.legend(facecolor='#222', labelcolor='white')
|
|
|
|
| 655 |
plt.tight_layout()
|
| 656 |
else:
|
| 657 |
fig = None
|
| 658 |
+
except Exception: fig = None
|
|
|
|
|
|
|
|
|
|
| 659 |
|
| 660 |
+
return status_text, status_md, symbol_md, pnl_md, details_md, fig, watchlist_df, wallet_md, history_md
|
| 661 |
|
| 662 |
except Exception as e:
|
| 663 |
+
return status_text, status_md, f"Error: {e}", "...", "...", None, empty_watchlist, wallet_md, history_md
|
|
|
|
| 664 |
|
| 665 |
async def run_cycle_from_gradio():
|
| 666 |
+
if sys_state.cycle_running: return "الدورة تعمل بالفعل."
|
|
|
|
| 667 |
await run_unified_cycle()
|
| 668 |
return sys_state.last_cycle_logs
|
| 669 |
|
|
|
|
| 671 |
# 🚀 بناء وتثبيت واجهة Gradio
|
| 672 |
# ==============================================================================
|
| 673 |
def create_gradio_ui():
|
| 674 |
+
css = """
|
| 675 |
+
#pnl_md { text-align: center; }
|
| 676 |
+
.gradio-container { background-color: #0b0f19; }
|
| 677 |
+
"""
|
| 678 |
|
| 679 |
+
with gr.Blocks(title="Titan V18 Pro Dashboard", css=css, theme=gr.themes.Monochrome()) as demo:
|
| 680 |
+
gr.Markdown("# 🚀 Titan V18 Pro Trading Terminal")
|
| 681 |
|
| 682 |
+
# --- الصف العلوي: الشارت + الإحصائيات الحية ---
|
| 683 |
with gr.Row():
|
| 684 |
+
# العمود الأيسر: الشارت والمعلومات الفنية
|
| 685 |
with gr.Column(scale=3):
|
| 686 |
+
live_chart = gr.Plot(label="Chart")
|
| 687 |
+
with gr.Row():
|
| 688 |
+
live_symbol = gr.Markdown("### No Active Trade")
|
| 689 |
+
live_pnl = gr.Markdown("---", elem_id="pnl_md")
|
| 690 |
+
live_details = gr.Markdown("Waiting for data...")
|
| 691 |
+
|
| 692 |
+
# العمود الأيمن: المحفظة والإحصائيات
|
| 693 |
with gr.Column(scale=1):
|
| 694 |
+
wallet_output = gr.HTML(label="Wallet")
|
| 695 |
+
history_output = gr.HTML(label="Stats")
|
| 696 |
+
watchlist_output = gr.DataFrame(label="Watchlist")
|
| 697 |
|
| 698 |
gr.HTML("<hr>")
|
| 699 |
|
| 700 |
+
# --- الصف السفلي: التحكم والسجلات ---
|
| 701 |
with gr.Row():
|
| 702 |
with gr.Column(scale=1):
|
| 703 |
+
gr.Markdown("## 🎮 التحكم (Controls)")
|
| 704 |
+
|
| 705 |
+
with gr.Row():
|
| 706 |
+
run_cycle_btn = gr.Button("🚀 (1) بدء الدورة (Scan)", variant="primary")
|
| 707 |
+
close_trade_btn = gr.Button("🚨 (2) إغلاق الصفقة (Panic)", variant="stop")
|
| 708 |
+
|
| 709 |
+
gr.Markdown("## 📡 حالة النظام")
|
| 710 |
+
status_markdown = gr.Markdown("جاري التهيئة...")
|
| 711 |
+
|
| 712 |
+
# رسائل التنبيه
|
| 713 |
+
alert_box = gr.Textbox(label="تنبيهات النظام", interactive=False)
|
| 714 |
|
| 715 |
with gr.Column(scale=3):
|
| 716 |
+
gr.Markdown("## 📜 سجلات النظام (System Logs)")
|
| 717 |
cycle_logs_output = gr.Textbox(
|
| 718 |
+
lines=15,
|
|
|
|
| 719 |
autoscroll=True,
|
| 720 |
max_lines=100,
|
| 721 |
+
value="..."
|
| 722 |
)
|
| 723 |
|
| 724 |
+
# --- التفاعلات (Interactions) ---
|
| 725 |
+
|
| 726 |
+
# زر تشغيل الدورة
|
| 727 |
run_cycle_btn.click(
|
| 728 |
fn=run_cycle_from_gradio,
|
| 729 |
inputs=None,
|
| 730 |
+
outputs=cycle_logs_output
|
|
|
|
| 731 |
)
|
| 732 |
|
| 733 |
+
# زر الإغلاق اليدوي
|
| 734 |
+
close_trade_btn.click(
|
| 735 |
+
fn=manual_close_current_trade,
|
| 736 |
+
inputs=None,
|
| 737 |
+
outputs=alert_box
|
| 738 |
+
)
|
| 739 |
+
|
| 740 |
+
# التحديث الدوري (Timer)
|
| 741 |
demo.load(
|
| 742 |
fn=check_live_pnl_and_status,
|
| 743 |
inputs=None,
|
| 744 |
+
outputs=[
|
| 745 |
+
cycle_logs_output,
|
| 746 |
+
status_markdown,
|
| 747 |
+
live_symbol,
|
| 748 |
+
live_pnl,
|
| 749 |
+
live_details,
|
| 750 |
+
live_chart,
|
| 751 |
+
watchlist_output,
|
| 752 |
+
wallet_output, # New
|
| 753 |
+
history_output # New
|
| 754 |
+
]
|
| 755 |
)
|
| 756 |
|
| 757 |
+
timer = gr.Timer(5) # تحديث كل 5 ثواني
|
|
|
|
| 758 |
timer.tick(
|
| 759 |
fn=check_live_pnl_and_status,
|
| 760 |
inputs=None,
|
| 761 |
+
outputs=[
|
| 762 |
+
cycle_logs_output,
|
| 763 |
+
status_markdown,
|
| 764 |
+
live_symbol,
|
| 765 |
+
live_pnl,
|
| 766 |
+
live_details,
|
| 767 |
+
live_chart,
|
| 768 |
+
watchlist_output,
|
| 769 |
+
wallet_output, # New
|
| 770 |
+
history_output # New
|
| 771 |
+
]
|
| 772 |
)
|
| 773 |
|
| 774 |
return demo
|
|
|
|
| 777 |
app = gr.mount_gradio_app(app, gradio_dashboard, path="/")
|
| 778 |
|
| 779 |
# ==============================================================================
|
| 780 |
+
# 🏁 تشغيل الخادم
|
| 781 |
# ==============================================================================
|
| 782 |
if __name__ == "__main__":
|
| 783 |
import uvicorn
|