Riy777 commited on
Commit
dfca362
·
verified ·
1 Parent(s): 5caeb23

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +186 -216
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # app.py (V17.3 - FastAPI Core + Gradio UI - Timer Tick Fix)
2
  import os
3
  import sys
4
  import traceback
@@ -12,12 +12,12 @@ from io import StringIO
12
  from fastapi import FastAPI, HTTPException, BackgroundTasks
13
  from typing import List, Dict, Any
14
 
15
- # [ 💡 إضافة جديدة 💡 ] استيراد مكتبات الواجهة
16
  import gradio as gr
17
  import pandas as pd
18
  import matplotlib.pyplot as plt
19
  import matplotlib.dates as mdates
20
- plt.switch_backend('Agg') # (مهم لـ Matplotlib في بيئة خادم)
21
 
22
  # ==============================================================================
23
  # 📥 استيراد الوحدات الأساسية (Core Modules Imports)
@@ -39,7 +39,6 @@ except ImportError as e:
39
  # ==============================================================================
40
  # 🌐 المتغيرات العامة وحالة النظام (Global State)
41
  # ==============================================================================
42
- # (كل هذا يبقى كما هو)
43
  r2: R2Service = None
44
  data_manager: DataManager = None
45
  ml_processor: MLProcessor = None
@@ -59,7 +58,6 @@ UNIFIED_MODELS_DIR = os.path.join(BASE_DIR, "ml_models", "Unified_Models_V1")
59
  # ==============================================================================
60
  # 🔄 حالة النظام (System State)
61
  # ==============================================================================
62
- # (تبقى كما هي)
63
  class SystemState:
64
  def __init__(self):
65
  self.ready = False
@@ -67,16 +65,16 @@ class SystemState:
67
  self.last_cycle_time: datetime = None
68
  self.last_cycle_error = None
69
  self.app_start_time = datetime.now()
70
- # [ 💡 إضافة جديدة 💡 ] مخزن مؤقت لآخر سجلات الدورة
71
  self.last_cycle_logs = "النظام قيد التهيئة... يرجى الانتظار."
72
 
73
  def set_ready(self):
74
  self.ready = True
75
- self.last_cycle_logs = "✅ النظام جاهز. يمكنك بدء الدورة."
76
 
77
  def set_cycle_start(self):
78
  self.cycle_running = True
79
- self.last_cycle_logs = "🌀 [Cycle START] جاري البحث عن إشارات..."
80
 
81
  def set_cycle_end(self, error=None, logs=None):
82
  self.cycle_running = False
@@ -87,50 +85,56 @@ class SystemState:
87
  elif error:
88
  self.last_cycle_logs = f"❌ [Cycle ERROR] {error}"
89
  else:
90
- self.last_cycle_logs = f"✅ [Cycle END] {datetime.now().strftime('%H:%M:%S')}. الدورة اكتملت."
91
 
92
  sys_state = SystemState()
93
 
94
  # ==============================================================================
95
  # 🚀 تهيئة التطبيق ودورة الحياة (Lifespan)
96
  # ==============================================================================
97
- # (دالة lifespan تبقى كما هي 100%)
98
  @asynccontextmanager
99
  async def lifespan(app: FastAPI):
100
  global r2, data_manager, ml_processor, trade_manager, oracle_engine
101
  global whale_monitor, news_fetcher, senti_analyzer, guard_engine, sniper_engine, sys_state
102
 
103
- print("🚀 [FastAPI] بدء التشغيل (Startup Event)...")
104
  print("------------------------------------------------------")
105
 
106
  try:
107
- # ( ... كل كود التهيئة ... )
108
  print(" [1/8] تهيئة R2Service...")
109
  r2 = R2Service()
 
110
  print(" [2/8] تهيئة DataManager...")
111
  data_manager = DataManager(contracts_db={}, whale_monitor=None, r2_service=r2)
112
  await data_manager.initialize()
113
  await data_manager.load_contracts_from_r2()
 
114
  print(" [3/8] تهيئة L2 Services (Whales, News, Sentiment)...")
115
  whale_monitor = EnhancedWhaleMonitor(contracts_db=data_manager.get_contracts_db(), r2_service=r2)
116
  news_fetcher = NewsFetcher()
117
  senti_analyzer = SentimentIntensityAnalyzer()
118
  data_manager.whale_monitor = whale_monitor
 
119
  print(f" [4/8] تهيئة OracleEngine (L3 Brain) من {UNIFIED_MODELS_DIR}...")
120
  oracle_engine = OracleEngine(model_dir=UNIFIED_MODELS_DIR)
121
  await oracle_engine.initialize()
 
122
  print(" [5/8] (تم تخطي LearningHub - مدمج في Oracle)")
 
123
  print(" [6/8] تهيئة MLProcessor (L1 Engine)...")
124
  ml_processor = MLProcessor(market_context=None, data_manager=data_manager, learning_hub=None)
125
  await ml_processor.initialize()
 
126
  print(f" -> [Guard Path] استخدام المسار المشترك للحارس: {SHARED_GUARD_MODELS_DIR}")
 
127
  print(" [7/8] تهيئة GuardEngine (L2 Exit Protector)...")
128
  guard_engine = GuardEngine(models_dir=SHARED_GUARD_MODELS_DIR)
129
  await guard_engine.initialize()
 
130
  print(" [8/8] تهيئة SniperEngine (L2 Entry Sniper)...")
131
  sniper_engine = SniperEngine(models_dir=SHARED_GUARD_MODELS_DIR)
132
  await sniper_engine.initialize()
133
- sniper_engine.set_entry_threshold(0.35) # (العتبة الجديدة 35%)
134
 
135
  print(" [FINAL] تهيئة TradeManager...")
136
  trade_manager = TradeManager(
@@ -141,6 +145,7 @@ async def lifespan(app: FastAPI):
141
  guard_engine=guard_engine,
142
  sniper_engine=sniper_engine
143
  )
 
144
  await trade_manager.initialize_sentry_exchanges()
145
  await trade_manager.start_sentry_loops()
146
 
@@ -156,16 +161,15 @@ async def lifespan(app: FastAPI):
156
  traceback.print_exc()
157
 
158
  finally:
159
- print("\n🛑 [FastAPI] بدء إيقاف التشغيل (Shutdown Event)...")
160
  sys_state.ready = False
161
  if trade_manager: await trade_manager.stop_sentry_loops()
162
  if data_manager: await data_manager.close()
163
  print("✅ [System SHUTDOWN] تم إغلاق جميع الاتصالات.")
164
 
165
  # ==============================================================================
166
- # 🧠 دوال التحليل المساعدة (تبقى كما هي)
167
  # ==============================================================================
168
- # (دالة _analyze_symbol_task تبقى كما هي 100%)
169
  async def _analyze_symbol_task(symbol: str) -> Dict[str, Any]:
170
  global data_manager, ml_processor
171
  try:
@@ -187,13 +191,14 @@ async def _analyze_symbol_task(symbol: str) -> Dict[str, Any]:
187
  return None
188
 
189
  # ==============================================================================
190
- # [ 🚀 🚀 🚀 ] الدورة الموحدة (النسخة الكاملة V15.17 - Batch Selection)
191
  # ==============================================================================
192
- # (دالة run_unified_cycle تبقى كما هي 100%)
193
  async def run_unified_cycle():
194
  """
195
- دورة التحليل الكاملة: L1 -> L2 -> L3 (Oracle) -> L4 (Sniper Batch).
196
- (معدلة لتخزين السجلات في sys_state)
 
 
197
  """
198
 
199
  # (إعداد التقاط السجلات)
@@ -216,6 +221,51 @@ async def run_unified_cycle():
216
  log_and_print(f"\n🌀 [Cycle START] {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
217
 
218
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  # --- 1. الغربلة الأولية (L1) ---
220
  log_and_print(" [Cycle 1/5] 🔍 بدء الغربلة السريعة (L1 Screening)...")
221
  candidates_to_analyze = await data_manager.layer1_rapid_screening()
@@ -249,6 +299,7 @@ async def run_unified_cycle():
249
  sys_state.set_cycle_end(logs=log_buffer.getvalue()); return
250
 
251
  # --- 2. التعزيز وحساب نقاط الطبقة الثانية (L2 Boosting) ---
 
252
  log_and_print(f"\n [Cycle 2/5] 🚀 بدء تعزيز النقاط (L2 Boosting) لأفضل 10 عملات...")
253
  l2_enriched_candidates = []
254
 
@@ -267,7 +318,7 @@ async def run_unified_cycle():
267
  if net_flow <= -500000: whale_score = 0.10
268
  elif net_flow < 0: whale_score = 0.05
269
  except Exception as e: log_and_print(f" ⚠️ [Whale Error] {symbol}: {e}")
270
- candidate['whale_data'] = {} # (توفير الذاكرة)
271
 
272
  # (ب. الأخبار)
273
  try:
@@ -280,7 +331,7 @@ async def run_unified_cycle():
280
  if compound >= 0.5: news_score = 0.05
281
  elif compound >= 0.1: news_score = 0.02
282
  except Exception as e: log_and_print(f" ⚠️ [News Error] {symbol}: {e}")
283
- candidate['news_text'] = "..." # (توفير الذاكرة)
284
 
285
  # (ج. مونت كارلو المتقدمة)
286
  try:
@@ -333,6 +384,12 @@ async def run_unified_cycle():
333
  oracle_decision = await oracle_engine.predict(signal)
334
  action = oracle_decision.get('action'); confidence = oracle_decision.get('confidence', 0.0)
335
  reason = oracle_decision.get('analysis_summary') or oracle_decision.get('reason', 'No summary provided')
 
 
 
 
 
 
336
  if action == 'WATCH':
337
  log_and_print(f" 🔥 [Oracle APPROVED] {symbol} (Conf: {confidence:.2f})")
338
  log_and_print(f" 📝 Reason: {reason}")
@@ -353,7 +410,7 @@ async def run_unified_cycle():
353
  if oracle_approved_signals:
354
  log_and_print(f" -> 🎯 [L4 Sniper Batch] إرسال {len(oracle_approved_signals)} إشارة للقناص للفرز النهائي واختيار الأفضل...")
355
 
356
- # [ 💡 تعديل 💡 ] تحويل طباعة TradeManager إلى السجل
357
  tm_log_buffer = StringIO()
358
  with redirect_stdout(tm_log_buffer), redirect_stderr(tm_log_buffer):
359
  await trade_manager.select_and_execute_best_signal(oracle_approved_signals)
@@ -376,244 +433,157 @@ async def run_unified_cycle():
376
  sys_state.set_cycle_end(error=e, logs=log_buffer.getvalue())
377
 
378
  # ==============================================================================
379
- # 🚦 نقاط النهاية (FastAPI Endpoints)
380
  # ==============================================================================
381
- # (هذا هو تطبيق FastAPI الرئيسي)
382
- app = FastAPI(
383
- lifespan=lifespan,
384
- title="Titan V17 (FastAPI Core + Gradio UI)",
385
- description="نظام تداول آلي هجين (L1/L2/L3-ML) مع واجهة مراقبة Gradio مدمجة"
386
- )
387
-
388
- # (نقطة النهاية الجذرية الأصلية - سيتم استبدالها بـ Gradio)
389
- # @app.get("/")
390
- # async def root_status(): ... (تم التعليق)
391
-
392
- @app.get("/run-cycle")
393
- async def trigger_cycle_endpoint(background_tasks: BackgroundTasks):
394
- """
395
- (تبقى كما هي 100% - الخادم الخارجي يعتمد عليها)
396
- """
397
- if not sys_state.ready:
398
- raise HTTPException(status_code=503, detail="System is still initializing.")
399
- if sys_state.cycle_running:
400
- raise HTTPException(status_code=429, detail="Cycle is already running.")
401
-
402
- background_tasks.add_task(run_unified_cycle)
403
- return {"status": "ACCEPTED", "message": "Unified smart cycle triggered (Batch Mode)."}
404
-
405
- @app.get("/status")
406
- async def get_full_status():
407
- """
408
- (تبقى كما هي 100% - يمكن للخادم الخارجي استخدامها لإبقاء البوت مستيقظاً)
409
- """
410
- return {
411
- "initialized": sys_state.ready,
412
- "cycle_running": sys_state.cycle_running,
413
- "active_trades_count": len(trade_manager.open_positions) if trade_manager else 0,
414
- "active_trades": list(trade_manager.open_positions.keys()) if trade_manager else [],
415
- "sentry_watchlist_count": len(trade_manager.watchlist) if trade_manager else 0,
416
- "sentry_watchlist": list(trade_manager.watchlist.keys()) if trade_manager else []
417
- }
418
 
419
  # ==============================================================================
420
- # 📊 [ 💡 إضافة جديدة 💡 ] دوال الواجهة (Gradio Helpers)
421
  # ==============================================================================
422
-
423
  async def check_live_pnl_and_status():
424
- """
425
- [جديد] دالة يتم استدعاؤها كل 10 ثواني لتحديث الواجهة.
426
- (هذه الدالة ليست Endpoint، هي دالة Python عادية تستدعيها Gradio)
427
- """
428
- global trade_manager, data_manager, sys_state
429
-
430
  if not sys_state.ready:
431
- return "النظام قيد التهيئة...", "...", "...", "...", "...", None, pd.DataFrame()
432
-
433
- # 1. تحديث حالة النظام وقائمة المراقبة
434
- try:
435
- status_text = sys_state.last_cycle_logs # (يعرض آخر سجلات الدورة)
436
- trade_count = len(trade_manager.open_positions)
437
- watch_count = len(trade_manager.watchlist)
438
- status_md = f"""
439
- **الحالة:** {'<span style="color:green;">جاهز</span>' if not sys_state.cycle_running else '<span style="color:orange;">تحليل...</span>'} |
440
- **الصفقات الموفتوحة:** {trade_count} |
441
- **قائمة المراقبة:** {watch_count}
442
- """
443
-
444
- watchlist_df = pd.DataFrame(list(trade_manager.watchlist.keys()), columns=["عملات المراقبة"])
445
- except Exception as e:
446
- return f"خطأ في جلب الحالة: {e}", "...", "...", "...", "...", None, pd.DataFrame()
447
 
448
- # 2. التحقق من الصفقة المفتوحة
449
- if not trade_manager.open_positions:
450
- return status_text, status_md, "لا توجد صفقة مفتوحة", "---", "---", None, watchlist_df
451
 
 
452
  try:
453
  symbol = list(trade_manager.open_positions.keys())[0]
454
  trade = trade_manager.open_positions[symbol]
455
 
456
- # 3. جلب السعر الحي
457
- current_price = await data_manager.get_latest_price_async(symbol)
458
- if current_price == 0.0:
459
- return status_text, status_md, f"{symbol} (جاري جلب السعر...)", "...", "...", None, watchlist_df
460
-
461
- entry_price = trade['entry_price']
462
- pnl_pct = ((current_price - entry_price) / entry_price) * 100
463
 
464
- # 4. تحديد اللون
465
- if pnl_pct > 0:
466
- color = "green"
467
- pnl_text = f"ربح {pnl_pct:.2f}%"
468
- elif pnl_pct < 0:
469
- color = "red"
470
- pnl_text = f"خسارة {pnl_pct:.2f}%"
471
  else:
472
- color = "gray"
473
- pnl_text = "تعادل 0.00%"
474
 
475
- symbol_md = f"### {symbol}"
476
- pnl_md = f"<h2 style='color:{color}; text-align:center;'>{pnl_text}</h2>"
477
- details_md = f"""
478
- **سعر الدخول:** {entry_price:.4f}
479
- **السعر الحالي:** {current_price:.4f}
480
- **جني الأرباح (TP):** {trade['tp_price']:.4f}
481
- **وقف الخسارة (SL):** {trade['sl_price']:.4f}
 
 
482
  """
 
 
483
 
484
- # 5. رسم المخطط
485
- fig = None
486
  try:
487
  ohlcv = await data_manager.get_latest_ohlcv(symbol, '5m', 100)
488
  if ohlcv:
489
- df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
490
- df['datetime'] = pd.to_datetime(df['timestamp'], unit='ms')
491
 
492
  plt.style.use('dark_background')
493
- fig, ax = plt.subplots(figsize=(10, 5))
494
- df.plot(x='datetime', y='close', ax=ax, label=f'سعر {symbol} (5m)', color='cyan')
495
- ax.axhline(y=entry_price, color='gray', linestyle='--', label=f'الدخول ({entry_price:.4f})')
496
- ax.axhline(y=trade['tp_price'], color='green', linestyle=':', label=f'TP ({trade['tp_price']:.4f})')
497
- ax.axhline(y=trade['sl_price'], color='red', linestyle=':', label=f'SL ({trade['sl_price']:.4f})')
498
- ax.legend()
499
- ax.set_title(f"مخطط {symbol} الحي")
500
- ax.set_xlabel("الوقت")
501
- ax.set_ylabel("السعر (USDT)")
502
- ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
503
  plt.tight_layout()
504
- except Exception as e:
505
- print(f"❌ خطأ في رسم المخطط: {e}")
 
 
506
  fig = None
507
-
508
- return status_text, status_md, symbol_md, pnl_md, details_md, fig, watchlist_df
509
 
510
- except Exception as e:
511
- print(f"❌ خطأ فادح في check_live_pnl: {e}")
512
- return status_text, status_md, f"خطأ: {e}", "...", "...", None, watchlist_df
513
 
514
- async def run_cycle_from_gradio():
515
- """
516
- [جديد] دالة لتشغيل الدورة من زر الواجهة.
517
- تقوم بالتقاط السجلات وإعادتها.
518
- """
519
- if sys_state.cycle_running:
520
- return "الدورة قيد التشغيل بالفعل. يرجى الانتظار."
521
-
522
- # (استدعاء الدالة الأصلية المعدلة)
523
- await run_unified_cycle()
524
-
525
- # (إرجاع السجلات التي خزنتها الدالة الأصلية)
526
- return sys_state.last_cycle_logs
527
 
528
  # ==============================================================================
529
- # 🚀 5. [ 💡 إضافة جديدة 💡 ] بناء وتثبيت واجهة Gradio
530
  # ==============================================================================
531
-
532
- def create_gradio_ui():
533
- """
534
- [جديد] إنشاء كائن واجهة Gradio.
535
- """
536
- css = """
537
- #pnl_md { text-align: center; }
538
- """
539
-
540
- with gr.Blocks(title="لوحة تحكم Titan", css=css, theme=gr.themes.Monochrome()) as demo:
541
- gr.Markdown("# 📊 لوحة تحكم Titan (مدمجة مع FastAPI)")
542
 
543
- # (الجزء العلوي: مراقبة ا��أرباح الحية)
544
  with gr.Row():
545
  with gr.Column(scale=3):
546
- live_chart = gr.Plot(label="المخطط الحي للصفقة")
547
  with gr.Column(scale=1):
548
- live_symbol = gr.Markdown("### لا توجد صفقة")
549
- live_pnl = gr.Markdown("---", elem_id="pnl_md")
550
- live_details = gr.Markdown("---")
551
-
552
- gr.HTML("<hr>") # فاصل
553
 
554
- # (الجزء السفلي: التحكم والسجلات)
555
  with gr.Row():
556
- with gr.Column(scale=1):
557
- gr.Markdown("## ⚙️ لوحة التحكم")
558
- run_cycle_btn = gr.Button("🚀 (1) بدء دورة التحليل يدوياً")
559
- gr.Markdown("## 📝 حالة النظام")
560
- status_markdown = gr.Markdown(f"**الحالة:** {'جاري التهيئة...' if not sys_state.ready else 'جاهز'}")
561
- watchlist_output = gr.DataFrame(label="قائمة المراقبة (Watchlist)")
562
 
563
- with gr.Column(scale=3):
564
- gr.Markdown("## 📜 سجلات آخر دورة تحليل")
565
- cycle_logs_output = gr.Textbox(
566
- label="السجلات",
567
- lines=20,
568
- autoscroll=True,
569
- max_lines=100,
570
- value="...يتم تحميل السجلات عند انتهاء التهيئة..."
571
- )
572
-
573
- # (المنطق التفاعلي)
574
 
575
- # 1. عند الضغط على الزر (لتشغيل الدورة يدوياً)
576
- run_cycle_btn.click(
577
- fn=run_cycle_from_gradio, # (استدعاء الدالة الوسيطة)
578
- inputs=None,
579
- outputs=cycle_logs_output,
580
- api_name="run_cycle_manual" # (يمكن استدعاؤها كـ API أيضاً)
581
  )
582
 
583
- # 2. التحديث التلقائي (كل 15 ثانية) - [ 💡💡💡 هذا هو الإصلاح 💡💡💡 ]
584
-
585
- # (أ) تحميل البيانات مرة واحدة عند فتح الصفحة
586
  demo.load(
587
- fn=check_live_pnl_and_status,
588
- inputs=None,
589
- outputs=[cycle_logs_output, status_markdown, live_symbol, live_pnl, live_details, live_chart, watchlist_output]
590
  )
591
 
592
- # (ب) إنشاء مشغل مخفي يعمل كل 15 ثانية
593
- timer = gr.Timer(15)
594
-
595
- # (ج) ربط المشغل بالدالة
596
- timer.tick(
597
- fn=check_live_pnl_and_status,
598
- inputs=None,
599
- outputs=[cycle_logs_output, status_markdown, live_symbol, live_pnl, live_details, live_chart, watchlist_output]
600
- )
601
-
602
  return demo
603
 
604
- # --- [ 💡 الأهم: دمج التطبيقين 💡 ] ---
605
- # 1. إنشاء واجهة Gradio
606
- gradio_dashboard = create_gradio_ui()
607
- # 2. تثبيت واجهة Gradio على تطبيق FastAPI في الرابط الرئيسي "/"
608
- app = gr.mount_gradio_app(app, gradio_dashboard, path="/")
609
- # (الآن `app` هو تطبيق FastAPI + Gradio المدمج)
610
-
611
-
612
  # ==============================================================================
613
- # 🏁 6. تشغيل الخادم المدمج
614
  # ==============================================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
615
  if __name__ == "__main__":
616
  import uvicorn
617
- port = int(os.environ.get("PORT", 7860))
618
- # (نقوم بتشغيل `app` المدمجة)
619
- uvicorn.run(app, host="0.0.0.0", port=port)
 
1
+ # app.py (V17.6 - Full Logic Restored + Interface Fixes)
2
  import os
3
  import sys
4
  import traceback
 
12
  from fastapi import FastAPI, HTTPException, BackgroundTasks
13
  from typing import List, Dict, Any
14
 
15
+ # [ استيراد مكتبات الواجهة ]
16
  import gradio as gr
17
  import pandas as pd
18
  import matplotlib.pyplot as plt
19
  import matplotlib.dates as mdates
20
+ plt.switch_backend('Agg') # مهم جداً لبيئة السيرفر
21
 
22
  # ==============================================================================
23
  # 📥 استيراد الوحدات الأساسية (Core Modules Imports)
 
39
  # ==============================================================================
40
  # 🌐 المتغيرات العامة وحالة النظام (Global State)
41
  # ==============================================================================
 
42
  r2: R2Service = None
43
  data_manager: DataManager = None
44
  ml_processor: MLProcessor = None
 
58
  # ==============================================================================
59
  # 🔄 حالة النظام (System State)
60
  # ==============================================================================
 
61
  class SystemState:
62
  def __init__(self):
63
  self.ready = False
 
65
  self.last_cycle_time: datetime = None
66
  self.last_cycle_error = None
67
  self.app_start_time = datetime.now()
68
+ # مخزن مؤقت لآخر سجلات الدورة
69
  self.last_cycle_logs = "النظام قيد التهيئة... يرجى الانتظار."
70
 
71
  def set_ready(self):
72
  self.ready = True
73
+ self.last_cycle_logs = "✅ النظام جاهز. اضغط 'بدء' للعمل."
74
 
75
  def set_cycle_start(self):
76
  self.cycle_running = True
77
+ self.last_cycle_logs = "🌀 [Cycle START] جاري العمل..."
78
 
79
  def set_cycle_end(self, error=None, logs=None):
80
  self.cycle_running = False
 
85
  elif error:
86
  self.last_cycle_logs = f"❌ [Cycle ERROR] {error}"
87
  else:
88
+ self.last_cycle_logs = f"✅ [Cycle END] {datetime.now().strftime('%H:%M:%S')}. الدورة انتهت."
89
 
90
  sys_state = SystemState()
91
 
92
  # ==============================================================================
93
  # 🚀 تهيئة التطبيق ودورة الحياة (Lifespan)
94
  # ==============================================================================
 
95
  @asynccontextmanager
96
  async def lifespan(app: FastAPI):
97
  global r2, data_manager, ml_processor, trade_manager, oracle_engine
98
  global whale_monitor, news_fetcher, senti_analyzer, guard_engine, sniper_engine, sys_state
99
 
100
+ print("🚀 [System] Starting Up (V17.6 Full)...")
101
  print("------------------------------------------------------")
102
 
103
  try:
 
104
  print(" [1/8] تهيئة R2Service...")
105
  r2 = R2Service()
106
+
107
  print(" [2/8] تهيئة DataManager...")
108
  data_manager = DataManager(contracts_db={}, whale_monitor=None, r2_service=r2)
109
  await data_manager.initialize()
110
  await data_manager.load_contracts_from_r2()
111
+
112
  print(" [3/8] تهيئة L2 Services (Whales, News, Sentiment)...")
113
  whale_monitor = EnhancedWhaleMonitor(contracts_db=data_manager.get_contracts_db(), r2_service=r2)
114
  news_fetcher = NewsFetcher()
115
  senti_analyzer = SentimentIntensityAnalyzer()
116
  data_manager.whale_monitor = whale_monitor
117
+
118
  print(f" [4/8] تهيئة OracleEngine (L3 Brain) من {UNIFIED_MODELS_DIR}...")
119
  oracle_engine = OracleEngine(model_dir=UNIFIED_MODELS_DIR)
120
  await oracle_engine.initialize()
121
+
122
  print(" [5/8] (تم تخطي LearningHub - مدمج في Oracle)")
123
+
124
  print(" [6/8] تهيئة MLProcessor (L1 Engine)...")
125
  ml_processor = MLProcessor(market_context=None, data_manager=data_manager, learning_hub=None)
126
  await ml_processor.initialize()
127
+
128
  print(f" -> [Guard Path] استخدام المسار المشترك للحارس: {SHARED_GUARD_MODELS_DIR}")
129
+
130
  print(" [7/8] تهيئة GuardEngine (L2 Exit Protector)...")
131
  guard_engine = GuardEngine(models_dir=SHARED_GUARD_MODELS_DIR)
132
  await guard_engine.initialize()
133
+
134
  print(" [8/8] تهيئة SniperEngine (L2 Entry Sniper)...")
135
  sniper_engine = SniperEngine(models_dir=SHARED_GUARD_MODELS_DIR)
136
  await sniper_engine.initialize()
137
+ sniper_engine.set_entry_threshold(0.35) # العتبة المحدثة
138
 
139
  print(" [FINAL] تهيئة TradeManager...")
140
  trade_manager = TradeManager(
 
145
  guard_engine=guard_engine,
146
  sniper_engine=sniper_engine
147
  )
148
+ # تحميل الحالة فوراً من R2
149
  await trade_manager.initialize_sentry_exchanges()
150
  await trade_manager.start_sentry_loops()
151
 
 
161
  traceback.print_exc()
162
 
163
  finally:
164
+ print("\n🛑 [System] بدء إيقاف التشغيل...")
165
  sys_state.ready = False
166
  if trade_manager: await trade_manager.stop_sentry_loops()
167
  if data_manager: await data_manager.close()
168
  print("✅ [System SHUTDOWN] تم إغلاق جميع الاتصالات.")
169
 
170
  # ==============================================================================
171
+ # 🧠 دوال التحليل المساعدة
172
  # ==============================================================================
 
173
  async def _analyze_symbol_task(symbol: str) -> Dict[str, Any]:
174
  global data_manager, ml_processor
175
  try:
 
191
  return None
192
 
193
  # ==============================================================================
194
+ # [ 🚀 🚀 🚀 ] الدورة الموحدة (Logic Restored + R2 Priority)
195
  # ==============================================================================
 
196
  async def run_unified_cycle():
197
  """
198
+ دورة التحليل الكاملة: L1 -> L2 -> L3 -> L4.
199
+ - تتحقق أولاً من وجود صفقات مفتوحة.
200
+ - إذا وجدت، تقوم بفحص صحي (Health Check) وتتوقف.
201
+ - إذا لم تجد، تبدأ البحث الكامل (Full Scan).
202
  """
203
 
204
  # (إعداد التقاط السجلات)
 
221
  log_and_print(f"\n🌀 [Cycle START] {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
222
 
223
  try:
224
+ # -----------------------------------------------------------
225
+ # 1. [ 🛑 الفحص الحاسم ] هل لدينا صفقة مفتوحة؟
226
+ # -----------------------------------------------------------
227
+ if trade_manager.open_positions:
228
+ symbol = list(trade_manager.open_positions.keys())[0]
229
+ log_and_print(f"⚠️ [System Limit] توجد صفقة مفتوحة بالفعل على {symbol}.")
230
+ log_and_print(f" -> 🛑 تم إلغاء البحث عن فرص جديدة (قاعدة الصفقة الواحدة).")
231
+ log_and_print(f" -> 🩺 جاري تشغيل 'إعادة التحليل' (Health Check) للصفقة الحالية...")
232
+
233
+ # --- إعادة تحليل الصفقة المفتوحة ---
234
+ analysis = await _analyze_symbol_task(symbol)
235
+ if analysis:
236
+ score = analysis.get('enhanced_final_score', 0.0)
237
+ comps = analysis.get('components', {})
238
+
239
+ log_and_print(f"\n📊 [Health Report] {symbol}")
240
+ log_and_print(f" - Current L1 Score: {score:.2f}")
241
+ log_and_print(f" - Titan: {comps.get('titan_score', 0):.2f} | Patterns: {comps.get('patterns_score', 0):.2f} | MC: {comps.get('mc_score', 0):.2f}")
242
+
243
+ # فحص العقل
244
+ log_and_print(" -> 🧠 استشارة Oracle للصفقة المفتوحة...")
245
+ oracle_res = await oracle_engine.predict(analysis)
246
+ action = oracle_res.get('action', 'WAIT')
247
+ conf = oracle_res.get('confidence', 0.0)
248
+ reason = oracle_res.get('analysis_summary') or oracle_res.get('reason')
249
+ log_and_print(f" -> Oracle Says: {action} (Conf: {conf:.2f})")
250
+ log_and_print(f" Reason: {reason}")
251
+
252
+ # فحص الحارس
253
+ log_and_print(" -> 🛡️ استشارة Guard V2 للخروج...")
254
+ if '5m' in analysis['ohlcv']:
255
+ guard_res = await guard_engine.check_exit_signal_async(analysis['ohlcv']['5m'])
256
+ log_and_print(f" -> Guard Says: {guard_res['action']} (Conf: {guard_res['confidence']:.2f})")
257
+
258
+ else:
259
+ log_and_print(f"⚠️ فشل تحليل البيانات لـ {symbol}.")
260
+
261
+ log_and_print(f"\n✅ انتهت دورة المتابعة. النظام يراقب {symbol}.")
262
+ sys_state.set_cycle_end(logs=log_buffer.getvalue())
263
+ return
264
+
265
+ # -----------------------------------------------------------
266
+ # 2. (اذا لم توجد صفقات) -> ابدأ البحث الكامل (Restored Logic)
267
+ # -----------------------------------------------------------
268
+
269
  # --- 1. الغربلة الأولية (L1) ---
270
  log_and_print(" [Cycle 1/5] 🔍 بدء الغربلة السريعة (L1 Screening)...")
271
  candidates_to_analyze = await data_manager.layer1_rapid_screening()
 
299
  sys_state.set_cycle_end(logs=log_buffer.getvalue()); return
300
 
301
  # --- 2. التعزيز وحساب نقاط الطبقة الثانية (L2 Boosting) ---
302
+ # [ تم استعادة الكود الكامل هنا ]
303
  log_and_print(f"\n [Cycle 2/5] 🚀 بدء تعزيز النقاط (L2 Boosting) لأفضل 10 عملات...")
304
  l2_enriched_candidates = []
305
 
 
318
  if net_flow <= -500000: whale_score = 0.10
319
  elif net_flow < 0: whale_score = 0.05
320
  except Exception as e: log_and_print(f" ⚠️ [Whale Error] {symbol}: {e}")
321
+ candidate['whale_data'] = {}
322
 
323
  # (ب. الأخبار)
324
  try:
 
331
  if compound >= 0.5: news_score = 0.05
332
  elif compound >= 0.1: news_score = 0.02
333
  except Exception as e: log_and_print(f" ⚠️ [News Error] {symbol}: {e}")
334
+ candidate['news_text'] = "..."
335
 
336
  # (ج. مونت كارلو المتقدمة)
337
  try:
 
384
  oracle_decision = await oracle_engine.predict(signal)
385
  action = oracle_decision.get('action'); confidence = oracle_decision.get('confidence', 0.0)
386
  reason = oracle_decision.get('analysis_summary') or oracle_decision.get('reason', 'No summary provided')
387
+
388
+ # [فلترة إشارات الشورت]
389
+ if "SHORT" in str(reason).upper() or "SHORT" in str(oracle_decision.get('strategy')).upper():
390
+ log_and_print(f" 🛑 [Oracle SKIP] {symbol} تم تجاهلها (إشارة بيع SHORT).")
391
+ continue
392
+
393
  if action == 'WATCH':
394
  log_and_print(f" 🔥 [Oracle APPROVED] {symbol} (Conf: {confidence:.2f})")
395
  log_and_print(f" 📝 Reason: {reason}")
 
410
  if oracle_approved_signals:
411
  log_and_print(f" -> 🎯 [L4 Sniper Batch] إرسال {len(oracle_approved_signals)} إشارة للقناص للفرز النهائي واختيار الأفضل...")
412
 
413
+ # التقاط سجلات TradeManager
414
  tm_log_buffer = StringIO()
415
  with redirect_stdout(tm_log_buffer), redirect_stderr(tm_log_buffer):
416
  await trade_manager.select_and_execute_best_signal(oracle_approved_signals)
 
433
  sys_state.set_cycle_end(error=e, logs=log_buffer.getvalue())
434
 
435
  # ==============================================================================
436
+ # 🔧 دوال مساعدة للواجهة (تنسيق الأرقام) - [جديد]
437
  # ==============================================================================
438
+ def fmt_price(price):
439
+ """تنسيق ذكي للأسعار: 8 خانات للعملات الصغيرة، 2 للكبيرة"""
440
+ if price is None or price == 0: return "0.00"
441
+ if price < 1.0: return f"{price:.8f}" # للعملات مثل SHIB
442
+ if price < 100: return f"{price:.4f}"
443
+ return f"{price:.2f}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
 
445
  # ==============================================================================
446
+ # 📊 واجهة Gradio وتحديث البيانات
447
  # ==============================================================================
 
448
  async def check_live_pnl_and_status():
 
 
 
 
 
 
449
  if not sys_state.ready:
450
+ return "جاري التهيئة...", "...", "...", "...", "...", None, pd.DataFrame()
451
+
452
+ # 1. الحالة العامة
453
+ log_text = sys_state.last_cycle_logs
454
+ n_trades = len(trade_manager.open_positions)
455
+ status_md = f"""
456
+ **الحالة:** {'<span style="color:green;">جاهز</span>' if not sys_state.cycle_running else '<span style="color:orange;">يحلل...</span>'} |
457
+ **الصفقات:** {n_trades} |
458
+ **المراقبة:** {len(trade_manager.watchlist)}
459
+ """
460
+ df_watch = pd.DataFrame(list(trade_manager.watchlist.keys()), columns=["Watchlist"])
 
 
 
 
 
461
 
462
+ # 2. لا توجد صفقات
463
+ if n_trades == 0:
464
+ return log_text, status_md, "### لا توجد صفقة نشطة", "---", "---", None, df_watch
465
 
466
+ # 3. عرض تفاصيل الصفقة (مع إصلاح الأصفار والمخطط)
467
  try:
468
  symbol = list(trade_manager.open_positions.keys())[0]
469
  trade = trade_manager.open_positions[symbol]
470
 
471
+ curr_price = await data_manager.get_latest_price_async(symbol)
472
+ entry = trade['entry_price']
 
 
 
 
 
473
 
474
+ if curr_price > 0 and entry > 0:
475
+ pnl = ((curr_price - entry) / entry) * 100
 
 
 
 
 
476
  else:
477
+ pnl = 0.0
 
478
 
479
+ color = "green" if pnl >= 0 else "red"
480
+ pnl_html = f"<h2 style='color:{color}; text-align:center;'>{pnl:.2f}%</h2>"
481
+
482
+ # [ إصلاح التنسيق ] استخدام fmt_price
483
+ details = f"""
484
+ **Entry:** {fmt_price(entry)}
485
+ **Current:** {fmt_price(curr_price)}
486
+ **TP:** {fmt_price(trade['tp_price'])}
487
+ **SL:** {fmt_price(trade['sl_price'])}
488
  """
489
+
490
+ symbol_md = f"### {symbol}"
491
 
492
+ # [ إصلاح المخطط ]
493
+ fig = plt.figure(figsize=(10, 5))
494
  try:
495
  ohlcv = await data_manager.get_latest_ohlcv(symbol, '5m', 100)
496
  if ohlcv:
497
+ df = pd.DataFrame(ohlcv, columns=['ts', 'o', 'h', 'l', 'c', 'v'])
498
+ df['dt'] = pd.to_datetime(df['ts'], unit='ms')
499
 
500
  plt.style.use('dark_background')
501
+ plt.plot(df['dt'], df['c'], label='Price', color='cyan')
502
+ plt.axhline(entry, color='gray', linestyle='--', label='Entry')
503
+ plt.axhline(trade['tp_price'], color='green', linestyle=':', label='TP')
504
+ plt.axhline(trade['sl_price'], color='red', linestyle=':', label='SL')
505
+ plt.title(f"{symbol} Live Chart")
506
+ plt.legend()
507
+ plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
 
 
 
508
  plt.tight_layout()
509
+ else:
510
+ plt.text(0.5, 0.5, "No Data", ha='center', color='white')
511
+ except:
512
+ plt.close()
513
  fig = None
 
 
514
 
515
+ return log_text, status_md, symbol_md, pnl_html, details, fig, df_watch
 
 
516
 
517
+ except Exception as e:
518
+ return log_text, status_md, f"Error: {e}", "...", "...", None, df_watch
 
 
 
 
 
 
 
 
 
 
 
519
 
520
  # ==============================================================================
521
+ # 📟 بناء الواجهة (UI Construction)
522
  # ==============================================================================
523
+ def create_ui():
524
+ with gr.Blocks(title="Titan Dashboard", theme=gr.themes.Monochrome()) as demo:
525
+ gr.Markdown("# 📊 Titan Automated Trading System")
 
 
 
 
 
 
 
 
526
 
 
527
  with gr.Row():
528
  with gr.Column(scale=3):
529
+ chart = gr.Plot(label="Chart")
530
  with gr.Column(scale=1):
531
+ sym_lbl = gr.Markdown("### ...")
532
+ pnl_lbl = gr.HTML("---") # HTML للألوان
533
+ det_lbl = gr.Markdown("Waiting...")
 
 
534
 
 
535
  with gr.Row():
536
+ btn = gr.Button("🚀 بدء دورة التحليل / فحص الحالة")
537
+ stat = gr.Markdown("**Status:** Initializing...")
538
+
539
+ with gr.Row():
540
+ logs = gr.Textbox(label="Logs", lines=15, max_lines=30, autoscroll=True)
541
+ watch = gr.DataFrame(label="Watchlist")
542
 
543
+ # Events
544
+ async def manual_run():
545
+ if sys_state.cycle_running: return "Busy..."
546
+ await run_unified_cycle()
547
+ return sys_state.last_cycle_logs
548
+
549
+ btn.click(manual_run, outputs=logs)
 
 
 
 
550
 
551
+ # Timer (للتحديث التلقائي)
552
+ timer = gr.Timer(10)
553
+ timer.tick(
554
+ check_live_pnl_and_status,
555
+ outputs=[logs, stat, sym_lbl, pnl_lbl, det_lbl, chart, watch]
 
556
  )
557
 
558
+ # Load on start (للتحميل الأولي)
 
 
559
  demo.load(
560
+ check_live_pnl_and_status,
561
+ outputs=[logs, stat, sym_lbl, pnl_lbl, det_lbl, chart, watch]
 
562
  )
563
 
 
 
 
 
 
 
 
 
 
 
564
  return demo
565
 
 
 
 
 
 
 
 
 
566
  # ==============================================================================
567
+ # 🔥 التشغيل (Application Entry Point)
568
  # ==============================================================================
569
+ app = FastAPI()
570
+
571
+ # نقاط النهاية للخادم الخارجي (Cron Jobs)
572
+ @app.get("/run-cycle")
573
+ async def api_run(bg: BackgroundTasks):
574
+ if sys_state.cycle_running: return {"status": "Busy"}
575
+ bg.add_task(run_unified_cycle)
576
+ return {"status": "Started"}
577
+
578
+ @app.get("/status")
579
+ async def api_status():
580
+ return {"trades": len(trade_manager.open_positions) if trade_manager else 0}
581
+
582
+ # دمج واجهة Gradio
583
+ ui = create_ui()
584
+ app = gr.mount_gradio_app(app, ui, path="/")
585
+
586
  if __name__ == "__main__":
587
  import uvicorn
588
+ # --no-access-log لإخفاء إزعاج الطلبات
589
+ uvicorn.run(app, host="0.0.0.0", port=7860, access_log=False)