Riy777 commited on
Commit
9fac145
·
verified ·
1 Parent(s): edb7934

Update trade_manager.py

Browse files
Files changed (1) hide show
  1. trade_manager.py +839 -473
trade_manager.py CHANGED
@@ -1,517 +1,883 @@
1
- # trade_manager.py (V22.4 - GEM-Architect: Full Logic + Multi-Stats + 300 Candle Fix)
 
 
 
2
  import asyncio
 
3
  import json
4
- import uuid
5
- import traceback
6
- from datetime import datetime
 
 
7
  from typing import List, Dict, Any
8
 
9
- class TradeManager:
10
- def __init__(self, r2_service, data_manager, titan_engine, pattern_engine, guard_engine=None, sniper_engine=None, deep_steward=None):
11
- self.r2 = r2_service
12
- self.data_manager = data_manager
13
- self.titan = titan_engine
14
- self.pattern_engine = pattern_engine
15
-
16
- # ⚠️ GuardEngine (Old V1) - معطل تماماً
17
- self.guard = guard_engine
18
-
19
- self.sniper = sniper_engine
20
-
21
- # [ 🧠 ] Hybrid Deep Steward (V2 Radar + V3 Sniper)
22
- self.deep_steward = deep_steward
23
-
24
- self.open_positions = {}
25
- self.watchlist = {}
26
- self.sentry_tasks = {}
27
- self.running = True
28
-
29
- # [ 🧠 IQ Stats ] مخزن الإحصائيات (سجلات فقط - المال في R2)
30
- # Hybrid: كل الصفقات التي أغلقها الذكاء الاصطناعي
31
- # V2: إحصائيات خاصة عندما كان V2 مرتفعاً
32
- # V3: إحصائيات خاصة عندما كان V3 مرتفعاً
33
- self.ai_stats = {
34
- "hybrid": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
35
- "v2": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
36
- "v3": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0}
37
- }
38
-
39
- # للإبقاء على التوافق مع الكود القديم الذي قد يطلب ds_stats مباشرة
40
- self.ds_stats = self.ai_stats["hybrid"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
- self.execution_lock = asyncio.Lock()
 
 
 
43
 
44
- print(f"🛡️ [TradeManager V22.4] Initialized. Hybrid Guardian Active (Limit: 300).")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
- async def initialize_sentry_exchanges(self):
47
- print("🛡️ [TradeManager] Initializing and syncing state with R2...")
48
- await self.sync_internal_state_with_r2()
 
 
 
 
 
 
 
 
 
49
 
50
- async def sync_internal_state_with_r2(self):
51
- try:
52
- open_trades_list = await self.r2.get_open_trades_async()
53
- self.open_positions = {trade['symbol']: trade for trade in open_trades_list}
54
- print(f" -> [Sync] تم استعادة {len(self.open_positions)} صفقة مفتوحة من R2.")
55
  except Exception as e:
56
- print(f" [TradeManager] فشل فادح في مزامنة R2 (get_open_trades_async): {e}")
57
- self.open_positions = {}
58
-
59
- # ==============================================================================
60
- # 🎯 دوال الفرز والتنفيذ (L4 Sniper Logic)
61
- # ==============================================================================
62
- async def select_and_execute_best_signal(self, oracle_approved_signals: List[Dict[str, Any]]):
63
- if len(self.open_positions) > 0:
64
- print(f"⛔ [TradeManager] تم إلغاء الفرز. توجد صفقة مفتوحة بالفعل.")
65
- return
66
-
67
- if self.sniper and not self.sniper.initialized:
68
- try:
69
- print(" -> [Lazy Load] 🎯 تهيئة SniperEngine V3 (Batch Select) عند الطلب...")
70
- await self.sniper.initialize()
71
- except Exception as e:
72
- print(f"[Lazy Load] فشل فادح في تهيئة SniperEngine: {e}")
73
- self.sniper = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
- if not self.sniper or not self.sniper.initialized:
76
- print("⚠️ [TradeManager] SniperEngine V3 غير جاهز للفرز. تم الإلغاء.")
77
- return
78
-
79
- sniper_candidates = []
80
 
81
- for signal in oracle_approved_signals:
82
- symbol = signal['symbol']
83
-
84
- if symbol in self.open_positions or symbol in self.watchlist:
85
- print(f" -> [L4 Skip] {symbol} موجودة مسبقاً في الصفقات أو المراقبة.")
86
- continue
87
-
88
- print(f" -> [L4 Check] {symbol} التحقق من L4 (Sniper)...")
89
- ohlcv_1m = await self.data_manager.get_latest_ohlcv(symbol, '1m', 600)
90
-
91
- if not ohlcv_1m or len(ohlcv_1m) < self.sniper.LOOKBACK_WINDOW:
92
- print(f" -> [L4 Skip] {symbol} بيانات 1m غير كافية للقناص ({len(ohlcv_1m)} < {self.sniper.LOOKBACK_WINDOW}).")
93
- continue
94
-
95
- sniper_result = await self.sniper.check_entry_signal_async(ohlcv_1m)
96
-
97
- if sniper_result['signal'] == 'BUY':
98
- confidence = sniper_result['confidence_prob']
99
- threshold = sniper_result.get('threshold', self.sniper.threshold)
100
- print(f" -> [L4 PASS] {symbol} (Conf: {confidence:.2f} >= {threshold:.2f}). أضيف للفرز النهائي.")
101
-
102
- signal['l2_sniper_result'] = sniper_result
103
- signal['sniper_confidence'] = confidence
104
- sniper_candidates.append(signal)
105
- else:
106
- confidence = sniper_result.get('confidence_prob', 0.0)
107
- threshold = sniper_result.get('threshold', self.sniper.threshold)
108
- print(f" -> [L4 REJECT] {symbol} (Conf: {confidence:.2f} < {threshold:.2f}).")
109
-
110
- if not sniper_candidates:
111
- print(" -> [L4 Result] القناص رفض جميع الإشارات المعتمدة من العقل. لا يوجد تنفيذ.")
112
- return
113
-
114
- sniper_candidates.sort(key=lambda x: (x['sniper_confidence'], x['final_total_score']), reverse=True)
115
 
116
- best_signal = sniper_candidates[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
- print(f" 🔥 [L4 WINNER] {best_signal['symbol']} (Sniper: {best_signal['sniper_confidence']:.2f}, L2: {best_signal['final_total_score']:.2f})")
119
 
120
- if len(sniper_candidates) > 1:
121
- for i, loser in enumerate(sniper_candidates[1:]):
122
- print(f" -> (خسرت أمامها: {i+2}. {loser['symbol']} (Sniper: {loser['sniper_confidence']:.2f}, L2: {loser['final_total_score']:.2f}))")
123
-
124
- async with self.execution_lock:
125
- if len(self.open_positions) > 0:
126
- print(f"⛔ [TradeManager] تم إلغاء التنفيذ في اللحظة الأخيرة. توجد صفقة مفتوحة.")
127
- return
128
 
129
- if best_signal['symbol'] in self.open_positions:
130
- print(f" -> [L4 EXEC SKIP] {best_signal['symbol']} تم فتحها بواسطة عملية أخرى للتو.")
131
- return
132
-
133
- await self._execute_entry_from_signal(best_signal['symbol'], best_signal)
134
-
135
- # ==============================================================================
136
- # 🎯 تنفيذ الدخول (Entry Execution)
137
- # ==============================================================================
138
- async def _execute_entry_from_signal(self, symbol, signal_data):
139
- try:
140
- if len(self.open_positions) > 0:
141
- print(f"⛔ [Entry BLOCKED] لا يمكن فتح {symbol}، توجد صفقة مفتوحة بالفعل.")
142
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
- trade_id = str(uuid.uuid4())
145
- current_price = float(signal_data.get('current_price', 0.0))
146
-
147
- if current_price <= 0.0:
148
- current_price = await self.data_manager.get_latest_price_async(symbol)
149
- if current_price <= 0.0:
150
- print(f"⚠️ [Entry ERROR] {symbol} السعر 0، لا يمكن التنفيذ.")
151
- return
152
-
153
- l2_result = signal_data.get('l2_sniper_result', {})
154
- l2_confidence = l2_result.get('confidence_prob', 0.0)
155
- l2_threshold = l2_result.get('threshold', 0.0)
156
-
157
- tp_price = signal_data.get('tp_price')
158
- sl_price = signal_data.get('sl_price')
159
-
160
- if not tp_price or not sl_price:
161
- print(f" -> [Entry Info] {symbol} لم يوفر العقل أهدافاً، جاري الحساب الوهمي.")
162
- atr_mock = current_price * 0.02
163
- tp_price = current_price + (atr_mock * 2.0)
164
- sl_price = current_price - (atr_mock * 1.0)
165
-
166
- tp_price = float(tp_price)
167
- sl_price = float(sl_price)
168
-
169
- if tp_price <= current_price or sl_price >= current_price:
170
- print(f"⚠️ [Entry ERROR] {symbol} أهداف TP/SL غير صالحة. إلغاء.")
171
- return
172
-
173
- new_trade = {
174
- 'id': trade_id,
175
- 'symbol': symbol,
176
- 'entry_price': current_price,
177
- 'entry_time': datetime.now().isoformat(),
178
- 'status': 'OPEN',
179
- 'tp_price': tp_price,
180
- 'sl_price': sl_price,
181
- 'last_update': datetime.now().isoformat(),
182
-
183
- 'l1_score': float(signal_data.get('final_total_score', 0.0)),
184
- 'l1_components': signal_data.get('components', {}),
185
- 'l2_sniper_confidence': float(l2_confidence),
186
- 'l2_sniper_threshold': float(l2_threshold),
187
- 'l3_oracle_tp': float(signal_data.get('tp_price', 0.0) or 0.0),
188
- 'l3_oracle_sl': float(signal_data.get('sl_price', 0.0) or 0.0),
189
- }
190
-
191
- self.open_positions[symbol] = new_trade
192
-
193
- if self.watchlist:
194
- print(f" -> [Watchlist] مسح {len(self.watchlist)} عنصر من قائمة المراقبة.")
195
- self.watchlist.clear()
196
-
197
- # ============================================================
198
- # 🕒 تسجيل وقت أول صفقة في تاريخ النظام (للعداد)
199
- # ============================================================
200
- portfolio = await self.r2.get_portfolio_state_async()
201
- if portfolio.get('first_trade_timestamp') is None:
202
- portfolio['first_trade_timestamp'] = new_trade['entry_time']
203
- await self.r2.save_portfolio_state_async(portfolio)
204
- print(f"🕒 [System Info] تم تسجيل توقيت أول صفقة في النظام: {new_trade['entry_time']}")
205
- # ============================================================
206
-
207
- await self.r2.save_open_trades_async(list(self.open_positions.values()))
208
 
209
- if symbol in self.sentry_tasks:
210
- self.sentry_tasks[symbol].cancel()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
 
212
- self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
 
 
213
 
214
- print(f"✅ [ENTRY EXECUTED] {symbol} | Price: {current_price:.8f} | TP: {tp_price:.8f} | SL: {sl_price:.8f}")
215
- print(f" -> L2 Score: {new_trade['l1_score']:.2f}, L4 Conf: {new_trade['l2_sniper_confidence']:.2f}")
216
-
217
- except Exception as e:
218
- print(f"❌ [TradeManager] فشل فادح أثناء تنفيذ الدخول لـ {symbol}: {e}")
219
- traceback.print_exc()
220
-
221
- # ==============================================================================
222
- # 👁️ Watchlist Functions
223
- # ==============================================================================
224
- async def _add_to_watchlist(self, symbol, signal_data):
225
- if symbol not in self.open_positions and symbol not in self.watchlist:
226
- if self.open_positions:
227
- print(f"👀 [Watchlist] {symbol} تم تجاهله (توجد صفقة أخرى مفتوحة).")
228
- return
229
-
230
- self.watchlist[symbol] = signal_data
231
- print(f"👀 [Watchlist] {symbol} تمت إضافته للمراقبة (L1 Score: {signal_data['enhanced_final_score']:.2f})")
232
-
233
- async def _find_signal_in_watchlist(self):
234
- if not self.watchlist:
235
- return
236
-
237
- if self.open_positions:
238
- print(" -> [Watchlist] تم تخطي البحث (توجد صفقة مفتوحة). مسح المراقب...")
239
- self.watchlist.clear()
240
- return
241
 
242
- if self.sniper and not self.sniper.initialized:
243
  try:
244
- print(" -> [Lazy Load] 🎯 تهيئة SniperEngine V3 (Watchlist) عند الطلب...")
245
- await self.sniper.initialize()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  except Exception as e:
247
- print(f" [Lazy Load] فشل فادح في تهيئة SniperEngine (Watchlist): {e}")
248
- self.sniper = None
249
-
250
- if not self.sniper or not self.sniper.initialized:
251
  return
252
 
253
- watchlist_symbols = list(self.watchlist.keys())
254
- if not watchlist_symbols:
255
- return
256
-
257
- print(f" -> [Watchlist] فحص {len(watchlist_symbols)} عملة في قائمة المراقبة لـ L4...")
258
 
259
- signals_from_watchlist = []
260
- for symbol in watchlist_symbols:
261
- signal_data = self.watchlist.get(symbol)
262
- if not signal_data: continue
263
- signals_from_watchlist.append(signal_data)
264
 
265
- if signals_from_watchlist:
266
- await self.select_and_execute_best_signal(signals_from_watchlist)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
 
268
- # ==============================================================================
269
- # 🛡️ دوال حارس الخروج (Hybrid Sentry)
270
- # ==============================================================================
271
- async def _guardian_loop(self, symbol: str):
272
- print(f"🛡️ [Sentry Activated] بدء الحراسة لـ {symbol} (فحص كل 60 ثانية).")
273
 
274
- while self.running and symbol in self.open_positions:
 
 
 
 
 
 
 
275
  try:
276
- # 🚀 1. الانتظار لمدة 60 ثانية
277
- await asyncio.sleep(60)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
 
279
- trade = self.open_positions.get(symbol)
280
- if not trade: break
281
-
282
- current_price = await self.data_manager.get_latest_price_async(symbol)
283
- if current_price == 0.0: continue
284
-
285
- # 🚀 2. فحص الحدود الصارمة (Hard Limits) - أمان أخير
286
- if current_price >= trade['tp_price']:
287
- print(f"✅ [Sentry EXIT] {symbol} (Hard TP Hit) at {current_price:.8f}")
288
- async with self.execution_lock:
289
- await self._execute_exit(symbol, current_price, "TP_HIT_HARD")
290
- break
291
-
292
- if current_price <= trade['sl_price']:
293
- print(f"🛑 [Sentry EXIT] {symbol} (Hard SL Hit) at {current_price:.8f}")
294
- async with self.execution_lock:
295
- await self._execute_exit(symbol, current_price, "SL_HIT_HARD")
296
- break
297
-
298
- # 🚀 3. [ 🧠 ] Hybrid DeepSteward (V2 + V3 Dual Brain)
299
- if self.deep_steward and self.deep_steward.initialized:
300
- # --- [تعديل جوهري] زيادة الحد إلى 300 شمعة لتجنب أخطاء النموذج ---
301
- task1 = self.data_manager.get_latest_ohlcv(symbol, '1m', 300)
302
- task5 = self.data_manager.get_latest_ohlcv(symbol, '5m', 200)
303
- task15 = self.data_manager.get_latest_ohlcv(symbol, '15m', 100)
304
-
305
- d1, d5, d15 = await asyncio.gather(task1, task5, task15)
306
-
307
- # التأكد من وجود بيانات كافية (V2 يحتاج 64 شمعة، المؤشرات تحتاج 200)
308
- if d1 and d5 and d15 and len(d1) >= 200:
309
- # استشارة الحارس الهجين
310
- decision = self.deep_steward.analyze_position(d1, d5, d15, trade['entry_price'])
311
- action = decision.get('action', 'HOLD')
312
- scores = decision.get('scores', {}) # درجات V2 و V3 للتدقيق
313
-
314
- if action == 'EXIT_HARD':
315
- print(f"🤖 [Hybrid Guardian] 🚨 خروج طارئ! {decision['reason']}")
316
- async with self.execution_lock:
317
- await self._execute_exit(symbol, current_price, "AI_HARD_EXIT", ai_scores=scores)
318
- break
319
-
320
- elif action == 'EXIT_SOFT':
321
- print(f"🤖 [Hybrid Guardian] ⚠️ تحذير خروج! {decision['reason']}")
322
- # هنا يمكن إضافة منطق لتشديد الستوب بدلاً من الخروج المباشر
323
- async with self.execution_lock:
324
- await self._execute_exit(symbol, current_price, "AI_SOFT_EXIT", ai_scores=scores)
325
- break
326
-
327
- else:
328
- print(f"⚠️ [Sentry] بيانات الحارس الهجين غير مكتملة لـ {symbol} (Rows: {len(d1) if d1 else 0}). تخطي الدورة.")
329
  else:
330
- pass # لا يوجد نموذج فعال
331
-
332
- self.open_positions[symbol]['last_update'] = datetime.now().isoformat()
333
-
334
- except asyncio.CancelledError:
335
- print(f"🛡️ [Sentry STOP] تم إيقاف الحارس لـ {symbol}.")
336
- break
337
  except Exception as e:
338
- print(f" [Sentry ERROR] فشل مؤقت لـ {symbol}: {e}")
339
- await asyncio.sleep(10)
340
-
341
- # ==============================================================================
342
- # 👻 المراقب الشبحي (Ghost Monitor) - [محدث لتوزيع الإحصائيات]
343
- # ==============================================================================
344
- def _launch_post_exit_analysis(self, symbol, exit_price, exit_time, position_size_usd, ai_scores=None):
345
- """يطلق مهمة خلفية لمراقبة السعر بعد الخروج وحساب الأثر بالدولار"""
346
- asyncio.create_task(self._analyze_after_exit_task(symbol, exit_price, exit_time, position_size_usd, ai_scores))
347
-
348
- def _update_specific_stat(self, key, is_good, usd_impact):
349
- """دالة مساعدة لتحديث إحصائيات محفظة معينة (Hybrid, V2, V3)"""
350
- if key not in self.ai_stats: return
351
- self.ai_stats[key]["total"] += 1
352
- if is_good:
353
- self.ai_stats[key]["good"] += 1
354
- self.ai_stats[key]["saved"] += abs(usd_impact)
355
- else:
356
- self.ai_stats[key]["missed"] += abs(usd_impact)
357
-
358
- async def _analyze_after_exit_task(self, symbol, exit_price, exit_time, position_size_usd, ai_scores):
359
- # ⏳ ننتظر 15 دقيقة (900 ثانية)
360
- await asyncio.sleep(900)
361
 
362
- try:
363
- current_price = await self.data_manager.get_latest_price_async(symbol)
364
- if current_price == 0: return
365
 
366
- # هل كان الخروج صحيحاً؟
367
- change_pct = (current_price - exit_price) / exit_price
368
- usd_impact = change_pct * position_size_usd
369
- is_good_exit = change_pct < 0 # جيد إذا السعر هبط بعد الخروج
370
-
371
- # 1. تحديث إحصائيات النظام الهجين (الكل)
372
- self._update_specific_stat("hybrid", is_good_exit, usd_impact)
373
 
374
- # 2. توزيع الفضل (Attribution) لـ V2 و V3
375
- if ai_scores:
376
- v2_score = ai_scores.get('v2', 0.0)
377
- v3_score = ai_scores.get('v3', 0.0)
378
-
379
- # V2 ينسب له الفضل إذا كانت درجة الخطر لديه عالية (فتح البوابة)
380
- if v2_score >= 0.60:
381
- self._update_specific_stat("v2", is_good_exit, usd_impact)
382
-
383
- # V3 ينسب له الفضل إذا كانت درجة الثقة لديه عالية (ضغط الزناد)
384
- if v3_score >= 0.75:
385
- self._update_specific_stat("v3", is_good_exit, usd_impact)
386
-
387
- # إعداد السجل لـ R2
388
- audit_record = {
389
- "symbol": symbol,
390
- "exit_time": exit_time,
391
- "exit_price": exit_price,
392
- "price_15m_later": current_price,
393
- "change_15m_pct": change_pct * 100,
394
- "usd_impact": usd_impact,
395
- "verdict": "SUCCESS (Saved Loss)" if is_good_exit else "MISS (Lost Profit)",
396
- "ai_scores": ai_scores if ai_scores else {},
397
- "timestamp": datetime.now().isoformat()
398
- }
399
 
400
- await self.r2.append_deep_steward_audit(audit_record)
401
- print(f"👻 [Ghost Monitor] {symbol}: {audit_record['verdict']} | Impact: ${usd_impact:.2f}")
402
-
403
- except Exception as e:
404
- print(f"⚠️ [Ghost Error] فشل تحليل ما بعد الخروج لـ {symbol}: {e}")
405
-
406
- # ==============================================================================
407
- # 🔴 دالة الخروج المحاسبية (Accounting Exit Logic)
408
- # ==============================================================================
409
- async def _execute_exit(self, symbol, price, reason, ai_scores=None):
410
- if symbol not in self.open_positions:
411
- print(f"⚠️ [Exit ERROR] {symbol} غير موجودة في الصفقات المفتوحة.")
412
- return
413
-
414
- try:
415
- # 1. استخراج بيانات الصفقة
416
- trade = self.open_positions.pop(symbol)
417
-
418
- entry_price = float(trade['entry_price'])
419
- exit_price = float(price)
420
-
421
- # 2. حساب نسبة الربح
422
- raw_profit_pct = (exit_price - entry_price) / entry_price
423
- profit_pct_display = raw_profit_pct * 100
424
-
425
- # 3. تحديث بيانات الصفقة
426
- trade['status'] = 'CLOSED'
427
- trade['exit_price'] = exit_price
428
- trade['exit_time'] = datetime.now().isoformat()
429
- trade['exit_reason'] = reason
430
- trade['profit_pct'] = profit_pct_display
431
-
432
- # 4. 💰 [المحاسبة] تحديث المحفظة (محفظة واحدة فقط)
433
- portfolio = await self.r2.get_portfolio_state_async()
434
- current_capital = float(portfolio.get('current_capital_usd', 100.0))
435
-
436
- pnl_usd = current_capital * raw_profit_pct
437
- new_capital = current_capital + pnl_usd
438
 
439
- # تحديث الإحصائيات التراكمية
440
- portfolio['current_capital_usd'] = new_capital
441
- portfolio['total_trades'] = portfolio.get('total_trades', 0) + 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
442
 
443
- if pnl_usd >= 0:
444
- portfolio['winning_trades'] = portfolio.get('winning_trades', 0) + 1
445
- portfolio['total_profit_usd'] = portfolio.get('total_profit_usd', 0.0) + pnl_usd
446
- trade['result'] = 'WIN'
447
- else:
448
- portfolio['losing_trades'] = portfolio.get('losing_trades', 0) + 1
449
- portfolio['total_loss_usd'] = portfolio.get('total_loss_usd', 0.0) + abs(pnl_usd)
450
- trade['result'] = 'LOSS'
451
-
452
- # تحديث نسبة الفوز
453
- total_t = portfolio['total_trades']
454
- if total_t > 0:
455
- portfolio['win_rate'] = (portfolio['winning_trades'] / total_t) * 100
456
 
457
- trade['pnl_usd'] = pnl_usd
 
458
 
459
- # 5. حفظ البيانات في R2
460
- await self.r2.save_portfolio_state_async(portfolio)
461
- await self.r2.save_open_trades_async(list(self.open_positions.values()))
462
- await self.r2.append_to_closed_trades_history(trade)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
 
464
- print(f"✅ [EXIT EXECUTED] {symbol} | Reason: {reason} | PnL: {profit_pct_display:.2f}% (${pnl_usd:.2f}) | New Cap: ${new_capital:.2f}")
465
-
466
- # 6. [ 🧠 ] إطلاق المراقب الشبحي إذا كان الخروج بسبب الذكاء الاصطناعي
467
- if "AI_" in reason:
468
- self._launch_post_exit_analysis(symbol, exit_price, trade['exit_time'], current_capital, ai_scores)
469
-
470
- # 7. تنظيف مهام الحارس
471
- if symbol in self.sentry_tasks:
472
- self.sentry_tasks[symbol].cancel()
473
- del self.sentry_tasks[symbol]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
474
 
475
- except Exception as e:
476
- print(f"❌ [TradeManager] فشل فادح أثناء تنفيذ الخروج لـ {symbol}: {e}")
477
- traceback.print_exc()
478
- if symbol not in self.open_positions:
479
- self.open_positions[symbol] = trade
480
-
481
- # [جديد] دالة للخروج القسري من واجهة المستخدم
482
- async def force_exit_by_manager(self, symbol, reason):
483
- print(f"⚠️ [Manager Force Exit] طلب خروج إداري لـ {symbol} بسبب: {reason}")
484
- current_price = await self.data_manager.get_latest_price_async(symbol)
485
- async with self.execution_lock:
486
- await self._execute_exit(symbol, current_price, reason)
487
-
488
- async def update_trade_targets(self, symbol, new_tp=None, new_sl=None, reason="MANUAL"):
489
- if symbol in self.open_positions:
490
- trade = self.open_positions[symbol]
491
- old_tp = trade['tp_price']
492
- old_sl = trade['sl_price']
493
-
494
- if new_tp is not None: trade['tp_price'] = float(new_tp)
495
- if new_sl is not None: trade['sl_price'] = float(new_sl)
496
 
497
- self.open_positions[symbol] = trade
498
- await self.r2.save_open_trades_async(list(self.open_positions.values()))
499
 
500
- print(f"🎯 [Targets Updated] {symbol} | TP: {old_tp:.8f}->{trade['tp_price']:.8f} | SL: {old_sl:.8f}->{trade['sl_price']:.8f} | Reason: {reason}")
501
- else:
502
- print(f"⚠️ [Target Update Failed] {symbol} is not currently open.")
503
-
504
- async def start_sentry_loops(self):
505
- for symbol in list(self.open_positions.keys()):
506
- if symbol not in self.sentry_tasks or self.sentry_tasks[symbol].done():
507
- print(f"🛡️ [Sentry Restart] Activating guardian for existing trade: {symbol}")
508
- self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
509
-
510
- async def stop_sentry_loops(self):
511
- print("🛑 [TradeManager] Stopping all sentry loops...")
512
- self.running = False
513
- for sym, task in self.sentry_tasks.items():
514
- task.cancel()
515
-
516
- await asyncio.sleep(1)
517
- print("🛑 [TradeManager] All sentries stopped.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py (V22.5 - GEM-Architect: FULL ORIGINAL SOURCE + Hybrid Guardian & Multi-Stats UI)
2
+ import os
3
+ import sys
4
+ import traceback
5
  import asyncio
6
+ import gc
7
  import json
8
+ import time
9
+ from datetime import datetime, timedelta
10
+ from contextlib import asynccontextmanager, redirect_stdout, redirect_stderr
11
+ 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 plotly.graph_objects as go
19
+
20
+ # ==============================================================================
21
+ # 📥 استيراد الوحدات الأساسية (Core Modules Imports)
22
+ # ==============================================================================
23
+ try:
24
+ from r2 import R2Service
25
+ from data_manager import DataManager
26
+ from ml_engine.processor import MLProcessor
27
+ from trade_manager import TradeManager
28
+ from ml_engine.oracle_engine import OracleEngine
29
+ from whale_monitor.core import EnhancedWhaleMonitor
30
+ from sentiment_news import NewsFetcher
31
+ from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
32
+ from ml_engine.sniper_engine import SniperEngine
33
+
34
+ # [ 🧠 الجوهرة ] استيراد الحارس الهجين (V4 Dual Brain)
35
+ from ml_engine.hybrid_guardian import HybridDeepSteward
36
+
37
+ except ImportError as e:
38
+ sys.exit(f"❌ [FATAL ERROR] Failed to import core modules: {e}")
39
+
40
+ # ==============================================================================
41
+ # 🌐 المتغيرات العامة وحالة النظام (Global State)
42
+ # ==============================================================================
43
+ r2: R2Service = None
44
+ data_manager: DataManager = None
45
+ ml_processor: MLProcessor = None
46
+ trade_manager: TradeManager = None
47
+ whale_monitor: EnhancedWhaleMonitor = None
48
+ news_fetcher: NewsFetcher = None
49
+ senti_analyzer: SentimentIntensityAnalyzer = None
50
+ sys_state: 'SystemState' = None
51
+ sniper_engine: SniperEngine = None
52
+ oracle_engine: OracleEngine = None
53
+
54
+ # [ 🧠 ] المتغير العام للنموذج الهجين
55
+ deep_steward: HybridDeepSteward = None
56
+
57
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
58
+ SHARED_GUARD_MODELS_DIR = os.path.join(BASE_DIR, "ml_models", "guard_v2")
59
+ UNIFIED_MODELS_DIR = os.path.join(BASE_DIR, "ml_models", "Unified_Models_V1")
60
+
61
+ # [ 🧠 ] مسارات النماذج للحارس الهجين
62
+ V2_MODEL_PATH = os.path.join(BASE_DIR, "ml_models", "DeepSteward_Production_V1.json")
63
+ V3_MODEL_PATH = os.path.join(BASE_DIR, "ml_models", "DeepSteward_V3_Production.json")
64
+ V3_FEATURES_PATH = os.path.join(BASE_DIR, "ml_models", "DeepSteward_V3_Features.json")
65
+
66
+ # ==============================================================================
67
+ # 🔄 حالة النظام (System State)
68
+ # ==============================================================================
69
+ class SystemState:
70
+ def __init__(self):
71
+ self.ready = False
72
+ self.cycle_running = False
73
+ self.auto_pilot = True # 🚀 [Auto-Pilot Default: ON]
74
+ self.last_cycle_time: datetime = None
75
+ self.last_cycle_error = None
76
+ self.app_start_time = datetime.now()
77
+ self.last_cycle_logs = "النظام قيد التهيئة... يرجى الانتظار."
78
+
79
+ def set_ready(self):
80
+ self.ready = True
81
+ self.last_cycle_logs = "✅ النظام جاهز. الطيار الآلي مفعل."
82
+
83
+ def set_cycle_start(self):
84
+ self.cycle_running = True
85
+ self.last_cycle_logs = "🌀 [Cycle START] جاري البحث عن إشارات..."
86
+
87
+ def set_cycle_end(self, error=None, logs=None):
88
+ self.cycle_running = False
89
+ self.last_cycle_time = datetime.now()
90
+ self.last_cycle_error = str(error) if error else None
91
+ if logs:
92
+ self.last_cycle_logs = logs
93
+ elif error:
94
+ self.last_cycle_logs = f"❌ [Cycle ERROR] {error}"
95
+ else:
96
+ self.last_cycle_logs = f"✅ [Cycle END] {datetime.now().strftime('%H:%M:%S')}. الدورة اكتملت."
97
+
98
+ sys_state = SystemState()
99
+
100
+ # ==============================================================================
101
+ # 🛠️ Helper Functions
102
+ # ==============================================================================
103
+ def format_crypto_price(price):
104
+ """
105
+ تنسيق السعر بذكاء ليظهر كاملاً بغض النظر عن عدد الأصفار.
106
+ """
107
+ if price is None: return "0.0"
108
+ try:
109
+ p = float(price)
110
+ if p == 0: return "0.0"
111
+ return "{:.8f}".format(p).rstrip('0').rstrip('.')
112
+ except:
113
+ return str(price)
114
+
115
+ def calculate_duration_str(timestamp_str):
116
+ """
117
+ دالة لحساب المدة الزمنية (Uptime/Trade Duration) داخل بايثون
118
+ """
119
+ if not timestamp_str:
120
+ return "--:--:--"
121
+ try:
122
+ try:
123
+ start_time = datetime.fromisoformat(str(timestamp_str))
124
+ except ValueError:
125
+ start_time = datetime.strptime(str(timestamp_str), "%Y-%m-%dT%H:%M:%S.%f")
126
+
127
+ now = datetime.now()
128
+ diff = now - start_time
129
+ total_seconds = int(diff.total_seconds())
130
 
131
+ days = total_seconds // 86400
132
+ hours = (total_seconds % 86400) // 3600
133
+ minutes = (total_seconds % 3600) // 60
134
+ seconds = total_seconds % 60
135
 
136
+ if days > 0:
137
+ return f"{days}d {hours:02}:{minutes:02}:{seconds:02}"
138
+ return f"{hours:02}:{minutes:02}:{seconds:02}"
139
+ except Exception:
140
+ return "--:--:--"
141
+
142
+ # ==============================================================================
143
+ # 🤖 [Auto-Pilot Daemon] حلقة الطيار الآلي
144
+ # ==============================================================================
145
+ async def auto_pilot_loop():
146
+ """
147
+ مهمة تعمل في الخلفية بشكل دائم للتحقق من خلو النظام من الصفقات وبدء التحليل فوراً.
148
+ """
149
+ print("🤖 [Auto-Pilot] المحرك يعمل في الخلفية...")
150
+ while True:
151
+ try:
152
+ # ننتظر قليلاً لحماية الموارد (10 ثواني راحة بين الفحوصات)
153
+ await asyncio.sleep(10)
154
 
155
+ if not sys_state.ready: continue
156
+
157
+ # الشروط الثلاثة للتشغيل التلقائي:
158
+ if sys_state.auto_pilot and not sys_state.cycle_running:
159
+ if trade_manager and len(trade_manager.open_positions) == 0:
160
+ # تشغيل الدورة
161
+ asyncio.create_task(run_unified_cycle())
162
+ # ننتظر فترة أطول قليلاً بعد إطلاق الدورة لمنع التكرار الفوري
163
+ await asyncio.sleep(5)
164
+ else:
165
+ # إذا كانت هناك صفقة، الطيار الآلي يراقب فقط ولا يتدخل في الدورة
166
+ pass
167
 
 
 
 
 
 
168
  except Exception as e:
169
+ print(f"⚠️ [Auto-Pilot Error] {e}")
170
+ await asyncio.sleep(30) # انتظار أطول عند الخطأ
171
+
172
+ # ==============================================================================
173
+ # 🚀 تهيئة التطبيق ودورة الحياة (Lifespan)
174
+ # ==============================================================================
175
+ @asynccontextmanager
176
+ async def lifespan(app: FastAPI):
177
+ global r2, data_manager, ml_processor, trade_manager, oracle_engine
178
+ global whale_monitor, news_fetcher, senti_analyzer, sniper_engine
179
+ global deep_steward, sys_state
180
+
181
+ print("🚀 [FastAPI] بدء التشغيل (Startup Event)...")
182
+ print("------------------------------------------------------")
183
+
184
+ try:
185
+ print(" [1/8] تهيئة R2Service...")
186
+ r2 = R2Service()
187
+ print(" [2/8] تهيئة DataManager...")
188
+ data_manager = DataManager(contracts_db={}, whale_monitor=None, r2_service=r2)
189
+ await data_manager.initialize()
190
+ await data_manager.load_contracts_from_r2()
191
+ print(" [3/8] تهيئة L2 Services (Whales, News, Sentiment)...")
192
+ whale_monitor = EnhancedWhaleMonitor(contracts_db=data_manager.get_contracts_db(), r2_service=r2)
193
+ news_fetcher = NewsFetcher()
194
+ senti_analyzer = SentimentIntensityAnalyzer()
195
+ data_manager.whale_monitor = whale_monitor
196
+ print(f" [4/8] تهيئة OracleEngine (L3 Brain) من {UNIFIED_MODELS_DIR}...")
197
+ oracle_engine = OracleEngine(model_dir=UNIFIED_MODELS_DIR)
198
+ await oracle_engine.initialize()
199
+ print(" [5/8] (تم تخطي LearningHub - مدمج في Oracle)")
200
+ print(" [6/8] تهيئة MLProcessor (L1 Engine)...")
201
+ ml_processor = MLProcessor(market_context=None, data_manager=data_manager, learning_hub=None)
202
+ await ml_processor.initialize()
203
 
204
+ print(" [7/8] تهيئة SniperEngine (L2 Entry Sniper)...")
205
+ sniper_engine = SniperEngine(models_dir=SHARED_GUARD_MODELS_DIR)
206
+ await sniper_engine.initialize()
207
+ sniper_engine.set_entry_threshold(0.35)
 
208
 
209
+ # [ 🧠 ] تهيئة Hybrid Deep Steward (V4 Dual Brain)
210
+ print(f" [8/8] تهيئة Hybrid Guardian (V2 + V3)...")
211
+ deep_steward = HybridDeepSteward(
212
+ v2_model_path=V2_MODEL_PATH,
213
+ v3_model_path=V3_MODEL_PATH,
214
+ v3_features_map_path=V3_FEATURES_PATH
215
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
 
217
+ if deep_steward.initialize():
218
+ print(" ✅ [Hybrid Guardian] جاهز للعمل: V2 (Radar) + V3 (Sniper).")
219
+ else:
220
+ print(" ⚠️ [Hybrid Guardian] فشل التحميل! سيعمل النظام بدون حماية AI.")
221
+
222
+ print(" [FINAL] تهيئة TradeManager...")
223
+ trade_manager = TradeManager(
224
+ r2_service=r2,
225
+ data_manager=data_manager,
226
+ titan_engine=ml_processor.titan,
227
+ pattern_engine=ml_processor.pattern_engine,
228
+ guard_engine=None,
229
+ sniper_engine=sniper_engine,
230
+ deep_steward=deep_steward # تمرير الحارس الهجين
231
+ )
232
+ await trade_manager.initialize_sentry_exchanges()
233
+ await trade_manager.start_sentry_loops()
234
 
235
+ sys_state.set_ready()
236
 
237
+ asyncio.create_task(auto_pilot_loop())
238
+ print(" -> 🤖 تم تشغيل خدمة الطيار الآلي (Auto-Pilot Daemon).")
 
 
 
 
 
 
239
 
240
+ print("------------------------------------------------------")
241
+ print(" [System READY] جميع الوحدات تم تهيئتها بنجاح.")
242
+ print("------------------------------------------------------")
243
+
244
+ yield
245
+
246
+ except Exception as e:
247
+ print(f"❌ [FATAL STARTUP ERROR] فشل فادح أثناء بدء التشغيل: {e}")
248
+ traceback.print_exc()
249
+
250
+ finally:
251
+ print("\n🛑 [FastAPI] بدء إيقاف التشغيل (Shutdown Event)...")
252
+ sys_state.ready = False
253
+ if trade_manager: await trade_manager.stop_sentry_loops()
254
+ if data_manager: await data_manager.close()
255
+ print("✅ [System SHUTDOWN] تم إغلاق جميع الاتصالات.")
256
+
257
+ # ==============================================================================
258
+ # 🧠 دوال التحليل المساعدة
259
+ # ==============================================================================
260
+ async def _analyze_symbol_task(symbol: str) -> Dict[str, Any]:
261
+ global data_manager, ml_processor
262
+ try:
263
+ required_tfs = ["5m", "15m", "1h", "4h", "1d"]
264
+ data_tasks = [data_manager.get_latest_ohlcv(symbol, tf, limit=250) for tf in required_tfs]
265
+ all_data = await asyncio.gather(*data_tasks)
266
+
267
+ ohlcv_data = {}
268
+ for tf, data in zip(required_tfs, all_data):
269
+ if data and len(data) > 0: ohlcv_data[tf] = data
270
 
271
+ if '5m' not in ohlcv_data or '15m' not in ohlcv_data or '1h' not in ohlcv_data or '4h' not in ohlcv_data:
272
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
 
274
+ current_price = await data_manager.get_latest_price_async(symbol)
275
+ raw_data = {'symbol': symbol, 'ohlcv': ohlcv_data, 'current_price': current_price}
276
+ return await ml_processor.process_and_score_symbol_enhanced(raw_data)
277
+ except Exception as e:
278
+ return None
279
+
280
+ # ==============================================================================
281
+ # [ 🚀 🚀 🚀 ] الدورة الموحدة (Unified Cycle)
282
+ # ==============================================================================
283
+ async def run_unified_cycle():
284
+
285
+ log_buffer = StringIO()
286
+
287
+ def log_and_print(message):
288
+ print(message)
289
+ log_buffer.write(message + '\n')
290
+
291
+ if sys_state.cycle_running:
292
+ log_and_print("⚠️ [Cycle] الدورة الحالية لا تزال قيد التشغيل. تم تجاهل الطلب.")
293
+ return
294
+
295
+ if not sys_state.ready:
296
+ log_and_print("⚠️ [Cycle] تم تجاهل الدورة، النظام لم يكتمل تهيئته.")
297
+ return
298
+
299
+ sys_state.set_cycle_start()
300
+ log_and_print(f"\n🌀 [Cycle START] {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
301
+
302
+ try:
303
+ # 1. التحقق الحاسم من الصفقات المفتوحة من R2
304
+ await trade_manager.sync_internal_state_with_r2()
305
+
306
+ # ============================================================
307
+ # 🔬 وضع الإدارة الذكية (Smart Management by Hybrid Guardian)
308
+ # ============================================================
309
+ if len(trade_manager.open_positions) > 0:
310
+ symbol = list(trade_manager.open_positions.keys())[0]
311
+ trade = trade_manager.open_positions[symbol]
312
 
313
+ log_and_print(f"🔒 [HybridGuardian] الصفقة {symbol} نشطة وتحت الحراسة المشددة.")
314
+ log_and_print(f" -> المراقبة تتم كل 60 ثانية في الخلفية (V2 Radar + V3 Sniper).")
315
+ log_and_print(f" -> باقي نماذج البحث (L1/L2/L3) في وضع السكون لتوفير الموارد.")
316
 
317
+ # فحص طوارئ سريع (فقط للتأكيد)
318
+ current_price = await data_manager.get_latest_price_async(symbol)
319
+ log_and_print(f" -> السعر الحالي: {format_crypto_price(current_price)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
 
321
+ # --- [NEW] استشارة الحارس وعرض الطوابع في السجلات ---
322
  try:
323
+ log_and_print(" -> 📡 جاري طلب تقرير الحارس المباشر (Consultation Scan)...")
324
+
325
+ # [CRITICAL FIX] رفع الحد إلى 300 شمعة لضمان عمل المؤشرات
326
+ t1 = data_manager.get_latest_ohlcv(symbol, '1m', 300)
327
+ t5 = data_manager.get_latest_ohlcv(symbol, '5m', 200)
328
+ t15 = data_manager.get_latest_ohlcv(symbol, '15m', 100)
329
+
330
+ d1, d5, d15 = await asyncio.gather(t1, t5, t15)
331
+
332
+ if d1 and d5 and d15 and len(d1) >= 200:
333
+ decision = deep_steward.analyze_position(d1, d5, d15, float(trade['entry_price']))
334
+ scores = decision.get('scores', {'v2': 0.0, 'v3': 0.0})
335
+
336
+ log_and_print(f"\n 📊 [GUARDIAN LIVE REPORT]")
337
+ log_and_print(f" • Decision: {decision.get('action')} ({decision.get('reason')})")
338
+ log_and_print(f" • V2 (Radar): {scores.get('v2', 0.0):.4f} [Safe < {deep_steward.V2_SAFE_LIMIT} | Panic > {deep_steward.V2_PANIC_TRIGGER}]")
339
+ log_and_print(f" • V3 (Sniper): {scores.get('v3', 0.0):.4f} [Hard > {deep_steward.V3_HARD_EXIT} | Ultra > {deep_steward.V3_ULTRA_CONF}]")
340
+ else:
341
+ log_and_print(" ⚠️ بيانات السوق غير كافية للتحليل اللحظي الآن (Requires 200+ candles).")
342
  except Exception as e:
343
+ log_and_print(f" ⚠️ تعذر عرض تقرير الحارس: {e}")
344
+
345
+ sys_state.set_cycle_end(logs=log_buffer.getvalue())
 
346
  return
347
 
348
+ # ============================================================
349
+ # 🔍 وضع البحث (Scanning Mode) - Normal Flow (مستعاد بالكامل)
350
+ # ============================================================
 
 
351
 
352
+ # --- 1. الغربلة الأولية (L1) ---
353
+ log_and_print(" [Cycle 1/5] 🔍 بدء الغربلة السريعة (L1 Screening)...")
354
+ candidates_to_analyze = await data_manager.layer1_rapid_screening()
 
 
355
 
356
+ if not candidates_to_analyze:
357
+ log_and_print("⚠️ [Cycle] لا توجد مرشحات من L1. إنهاء.")
358
+ sys_state.set_cycle_end(logs=log_buffer.getvalue()); return
359
+
360
+ log_and_print(f" -> 🧠 بدء تحليل L1 المعمق لـ {len(candidates_to_analyze)} عملة...")
361
+ analysis_start_time = time.time()
362
+
363
+ tasks = [_analyze_symbol_task(cand['symbol']) for cand in candidates_to_analyze if cand.get('symbol')]
364
+ results = await asyncio.gather(*tasks)
365
+
366
+ valid_l1_results = [res for res in results if res is not None]
367
+ top_10_l1 = sorted(valid_l1_results, key=lambda x: x.get('enhanced_final_score', 0.0), reverse=True)[:10]
368
+
369
+ log_and_print(f" -> 🧠 انتهى تحليل L1 في {time.time() - analysis_start_time:.2f} ثانية.")
370
+ log_and_print(f" - ✅ نجح: {len(valid_l1_results)} إشارة مرشحة.")
371
+
372
+ if top_10_l1:
373
+ log_and_print(f"\n -> 🥇 أعلى 10 درجات في الطبقة الأولى (L1):")
374
+ for i, res in enumerate(top_10_l1):
375
+ components = res.get('components', {})
376
+ titan_s = components.get('titan_score', 0.0)
377
+ pattern_s = components.get('patterns_score', 0.0)
378
+ mc_s = components.get('mc_score', 0.0)
379
+ log_and_print(f" {i+1}. {res.get('symbol'):<10}: {res.get('enhanced_final_score', 0.0):.4f} [T: {titan_s:.2f} | P: {pattern_s:.2f} | M: {mc_s:.2f}]")
380
+ else:
381
+ log_and_print(" -> ⚠️ لم يتم العثور على أي عملات صالحة.")
382
+ sys_state.set_cycle_end(logs=log_buffer.getvalue()); return
383
 
384
+ # --- 2. التعزيز وحساب نقاط الطبقة الثانية (L2 Boosting) ---
385
+ log_and_print(f"\n [Cycle 2/5] 🚀 بدء تعزيز النقاط (L2 Boosting) لأفضل 10 عملات...")
386
+ l2_enriched_candidates = []
 
 
387
 
388
+ for candidate in top_10_l1:
389
+ symbol = candidate['symbol']
390
+ l1_score = candidate['enhanced_final_score']
391
+ whale_score = 0.0; whale_raw_str = "No Data"
392
+ news_score = 0.0; news_raw_str = "No Data"
393
+ adv_mc_score = 0.0; mc_raw_str = "No Data"
394
+
395
+ # (أ. بيانات الحيتان)
396
  try:
397
+ whale_data = await whale_monitor.get_symbol_whale_activity(symbol)
398
+ net_flow = whale_data.get('accumulation_analysis_24h', {}).get('net_flow_usd', 0.0)
399
+ whale_raw_str = f"${net_flow:,.0f}"
400
+ if net_flow <= -500000: whale_score = 0.10
401
+ elif net_flow < 0: whale_score = 0.05
402
+ except Exception as e: log_and_print(f" ⚠️ [Whale Error] {symbol}: {e}")
403
+ candidate['whale_data'] = {}
404
+
405
+ # (ب. الأخبار)
406
+ try:
407
+ news_item = await news_fetcher.get_news(symbol)
408
+ if news_item:
409
+ news_text = news_item.get('summary', 'No news')
410
+ sentiment = senti_analyzer.polarity_scores(news_text)
411
+ compound = sentiment['compound']
412
+ news_raw_str = f"{compound:.2f}"
413
+ if compound >= 0.5: news_score = 0.05
414
+ elif compound >= 0.1: news_score = 0.02
415
+ except Exception as e: log_and_print(f" ⚠️ [News Error] {symbol}: {e}")
416
+ candidate['news_text'] = "..."
417
+
418
+ # (ج. مونت كارلو المتقدمة)
419
+ try:
420
+ full_ohlcv_1h = await data_manager.get_latest_ohlcv(symbol, '1h', limit=100)
421
+ if full_ohlcv_1h and len(full_ohlcv_1h) >= 30:
422
+ mc_data = {'1h': full_ohlcv_1h}
423
+ mc_res = await ml_processor.mc_analyzer.generate_1h_distribution_advanced(mc_data)
424
+ if mc_res and not mc_res.get('error', False):
425
+ prob_gain = mc_res.get('probability_of_gain', 0.5)
426
+ mc_raw_str = f"{prob_gain:.2f}"
427
+ if prob_gain > 0.5: adv_mc_score = min(0.10, (prob_gain - 0.5) * 0.20)
428
+ except Exception as e: log_and_print(f" ⚠️ [MC Exception] {symbol}: {e}")
429
 
430
+ final_score = l1_score + whale_score + news_score + adv_mc_score
431
+ candidate['final_total_score'] = final_score
432
+ candidate['l2_scores'] = {'whale': whale_score, 'news': news_score, 'adv_mc': adv_mc_score}
433
+ candidate['l2_raw_values'] = {'whale': whale_raw_str, 'news': news_raw_str, 'adv_mc': mc_raw_str}
434
+ l2_enriched_candidates.append(candidate)
435
+
436
+ # --- 3. إعادة الترتيب وطباعة الجدول ---
437
+ log_and_print(f" [Cycle 3/5] 📊 إعادة ترتيب المرشحين حسب الدرجة النهائية (L1 + L2)...")
438
+ sorted_finalists = sorted(l2_enriched_candidates, key=lambda x: x['final_total_score'], reverse=True)
439
+
440
+ log_and_print("\n" + "="*135)
441
+ header = f"{'SYMBOL':<10} | {'L1 SCORE':<8} | {'WHALE (Flow)':<22} | {'NEWS (Sent)':<18} | {'ADV MC (Prob)':<18} | {'FINAL':<8}"
442
+ log_and_print(header)
443
+ log_and_print("-" * 135)
444
+ for cand in sorted_finalists:
445
+ sym = cand['symbol']; l1 = cand['enhanced_final_score']; l2 = cand['l2_scores']; l2_raw = cand['l2_raw_values']; final = cand['final_total_score']
446
+ whale_cell = f"{l2['whale']:.2f} ({l2_raw['whale']})"
447
+ news_cell = f"{l2['news']:.2f} ({l2_raw['news']})"
448
+ mc_cell = f"{l2['adv_mc']:.2f} ({l2_raw['adv_mc']})"
449
+ log_and_print(f"{sym:<10} | {l1:.4f} | {whale_cell:<22} | {news_cell:<18} | {mc_cell:<18} | {final:.4f}")
450
+ log_and_print("="*135 + "\n")
451
+
452
+ # --- 4. اختيار أفضل 5 للنموذج الضخم (Oracle) ---
453
+ top_5_candidates = sorted_finalists[:5]
454
+ log_and_print(f" [Cycle 4/5] 🧠 إرسال أفضل {len(top_5_candidates)} عملة للعقل الاحتمالي (Oracle Engine)...")
455
+
456
+ oracle_start_time = time.time()
457
+ oracle_approved_signals = []
458
+
459
+ for signal in top_5_candidates:
460
+ symbol = signal['symbol']
461
+ if signal['final_total_score'] < 0.60:
462
+ log_and_print(f" -> 🛑 {symbol} تم استبعاده (الدرجة النهائية {signal['final_total_score']:.2f} < 0.60)")
463
+ continue
464
+ log_and_print(f" -> 🧠 [Oracle Scan] جاري فحص {symbol}...")
465
+ try:
466
+ oracle_decision = await oracle_engine.predict(signal)
467
+ action = oracle_decision.get('action'); confidence = oracle_decision.get('confidence', 0.0)
468
+ reason = oracle_decision.get('analysis_summary') or oracle_decision.get('reason', 'No summary provided')
469
+ if action == 'WATCH':
470
+ log_and_print(f" 🔥 [Oracle APPROVED] {symbol} (Conf: {confidence:.2f})")
471
+ log_and_print(f" 📝 Reason: {reason}")
472
+ signal['tp_price'] = oracle_decision.get('tp_price')
473
+ signal['sl_price'] = oracle_decision.get('sl_price')
474
+ oracle_approved_signals.append(signal)
 
 
 
 
 
475
  else:
476
+ log_and_print(f" 🛑 [Oracle REJECTED] {symbol} (Conf: {confidence:.2f})")
477
+ log_and_print(f" 📝 Reason: {reason[:200]}...")
 
 
 
 
 
478
  except Exception as e:
479
+ log_and_print(f" ⚠️ [Oracle Error] فشل تحليل Oracle لـ {symbol}: {e}")
480
+ traceback.print_exc(file=log_buffer)
481
+ continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
482
 
483
+ log_and_print(f" -> 🧠 انتهى فحص Oracle في {time.time() - oracle_start_time:.2f} ثانية. (تمت الموافقة على {len(oracle_approved_signals)} إشارة)")
 
 
484
 
485
+ # (إرسال المجموعة إلى TradeManager للفرز النهائي والتنفيذ)
486
+ if oracle_approved_signals:
487
+ log_and_print(f" -> 🎯 [L4 Sniper Batch] إرسال {len(oracle_approved_signals)} إشارة للقناص للفرز النهائي واختيار الأفضل...")
 
 
 
 
488
 
489
+ tm_log_buffer = StringIO()
490
+ with redirect_stdout(tm_log_buffer), redirect_stderr(tm_log_buffer):
491
+ await trade_manager.select_and_execute_best_signal(oracle_approved_signals)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
492
 
493
+ tm_logs = tm_log_buffer.getvalue()
494
+ print(tm_logs)
495
+ log_buffer.write(tm_logs + '\n')
496
+ else:
497
+ log_and_print(" -> 🎯 [L4 Sniper Batch] لا توجد إشارات معتمدة من العقل لإرسالها للقناص.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
498
 
499
+ # --- 5. الصيانة والتعلم ---
500
+ log_and_print(f" [Cycle 5/5] 🧹 تنظيف الذاكرة...")
501
+ gc.collect()
502
+ log_and_print(f"🌀 [Cycle END] {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
503
+ sys_state.set_cycle_end(logs=log_buffer.getvalue())
504
+
505
+ except Exception as e:
506
+ error_logs = f"❌ [Cycle ERROR] فشلت الدورة الموحدة: {e}\n{traceback.format_exc()}"
507
+ log_and_print(error_logs)
508
+ sys_state.set_cycle_end(error=e, logs=log_buffer.getvalue())
509
+
510
+ # ==============================================================================
511
+ # 🚦 نقاط النهاية (FastAPI Endpoints)
512
+ # ==============================================================================
513
+ app = FastAPI(
514
+ lifespan=lifespan,
515
+ title="Titan V22.5 Pro (Hybrid Guardian + MultiStats)",
516
+ description="نظام تداول آلي هجين (L1/L2/L3-ML) مع حارس ذكي مزدوج وواجهة Gradio مدمجة"
517
+ )
518
+
519
+ @app.get("/run-cycle")
520
+ async def trigger_cycle_endpoint(background_tasks: BackgroundTasks):
521
+ if not sys_state.ready:
522
+ raise HTTPException(status_code=503, detail="System is still initializing.")
523
+ if sys_state.cycle_running:
524
+ raise HTTPException(status_code=429, detail="Cycle is already running.")
525
+
526
+ background_tasks.add_task(run_unified_cycle)
527
+ return {"status": "ACCEPTED", "message": "Unified smart cycle triggered (Batch Mode)."}
528
+
529
+ @app.get("/status")
530
+ async def get_full_status():
531
+ return {
532
+ "initialized": sys_state.ready,
533
+ "cycle_running": sys_state.cycle_running,
534
+ "active_trades_count": len(trade_manager.open_positions) if trade_manager else 0,
535
+ "active_trades": list(trade_manager.open_positions.keys()) if trade_manager else [],
536
+ "sentry_watchlist_count": len(trade_manager.watchlist) if trade_manager else 0,
537
+ "sentry_watchlist": list(trade_manager.watchlist.keys()) if trade_manager else []
538
+ }
539
+
540
+ # ==============================================================================
541
+ # 📊 [ 💡 GEM-ARCHITECT: UI LOGIC - FULL RESTORE & FIX ]
542
+ # ==============================================================================
543
+
544
+ async def manual_close_current_trade():
545
+ """دالة زر الإ��لاق اليدوي"""
546
+ if not trade_manager.open_positions:
547
+ return "⚠️ لا توجد صفقة مفتوحة لإغلاقها."
548
+
549
+ symbol = list(trade_manager.open_positions.keys())[0]
550
+ await trade_manager.force_exit_by_manager(symbol, reason="MANUAL_UI_BUTTON")
551
+ return f"✅ تم إرسال أمر إغلاق فوري لـ {symbol}."
552
+
553
+ # [جديد] معالج زر التصفير
554
+ async def reset_stats_handler():
555
+ """معالج زر التصفير"""
556
+ if len(trade_manager.open_positions) > 0:
557
+ return "⚠️ خطأ: يجب إغلاق الصفقات المفتوحة أولاً قبل التصفير!"
558
+
559
+ success = await r2.reset_all_stats_async()
560
+ if success: return "✅ تم تصفير الحساب والسجلات بنجاح."
561
+ else: return "❌ فشل التصفير. راجع السجلات."
562
+
563
+ # تبديل وضع الطيار الآلي (On/Off)
564
+ async def toggle_auto_pilot(enable):
565
+ sys_state.auto_pilot = enable
566
+ status = "مفعل (ON)" if enable else "متوقف (OFF)"
567
+ return f"تم تغيير وضع الطيار الآلي إلى: {status}"
568
+
569
+ async def check_live_pnl_and_status(selected_view="Hybrid System"):
570
+ """
571
+ تحديث الواجهة الدورية (مع دعم القائمة المنسدلة للإحصائيات)
572
+ """
573
+ global trade_manager, data_manager, sys_state, r2
574
+
575
+ # Default Values
576
+ empty_chart = go.Figure()
577
+ empty_chart.update_layout(
578
+ template="plotly_dark", paper_bgcolor="#0b0f19", plot_bgcolor="#0b0f19",
579
+ xaxis={'visible': False}, yaxis={'visible': False},
580
+ annotations=[dict(text="Waiting for Signal...", x=0.5, y=0.5, showarrow=False, font=dict(color="gray", size=20))]
581
+ )
582
+ wl_df_empty = pd.DataFrame(columns=["Symbol", "Score"])
583
+
584
+ if not sys_state.ready:
585
+ return "Initializing...", "...", empty_chart, "0.0", "0.0", "0.0", "0.0", "0.0%", wl_df_empty, "Loading...", "Loading..."
586
+
587
+ try:
588
+ # 1. Fetch Portfolio & Time
589
+ portfolio = await r2.get_portfolio_state_async()
590
+ curr_cap = portfolio.get('current_capital_usd', 100.0)
591
+ first_ts = portfolio.get('first_trade_timestamp')
592
+
593
+ # 🕒 حساب وقت التشغيل (Uptime) في بايثون
594
+ uptime_str = calculate_duration_str(first_ts)
595
+
596
+ total_t = portfolio.get('total_trades', 0)
597
+ wins = portfolio.get('winning_trades', 0)
598
+ losses = portfolio.get('losing_trades', 0)
599
+ if losses == 0 and total_t > 0: losses = total_t - wins
600
+
601
+ tot_prof = portfolio.get('total_profit_usd', 0.0)
602
+ tot_loss = portfolio.get('total_loss_usd', 0.0)
603
+ net_prof = tot_prof - tot_loss
604
+ win_rate = (wins / total_t * 100) if total_t > 0 else 0.0
605
+
606
+ # 2. Active Trade Logic
607
+ symbol = None
608
+ entry_p = 0.0
609
+ tp_p = 0.0
610
+ sl_p = 0.0
611
+ curr_p = 0.0
612
+ pnl_val = 0.0
613
+ pnl_pct = 0.0
614
+ trade_dur_str = "--:--:--"
615
+
616
+ if trade_manager.open_positions:
617
+ symbol = list(trade_manager.open_positions.keys())[0]
618
+ trade = trade_manager.open_positions[symbol]
619
 
620
+ entry_p = float(trade.get('entry_price', 0.0))
621
+ tp_p = float(trade.get('tp_price', 0.0))
622
+ sl_p = float(trade.get('sl_price', 0.0))
 
 
 
 
 
 
 
 
 
 
623
 
624
+ # 🕒 حساب مدة الصفقة الحالية في بايثون
625
+ trade_dur_str = calculate_duration_str(trade.get('entry_time'))
626
 
627
+ curr_p = await data_manager.get_latest_price_async(symbol)
628
+ if curr_p > 0 and entry_p > 0:
629
+ pnl_pct = ((curr_p - entry_p) / entry_p) * 100
630
+ pnl_val = curr_cap * (pnl_pct / 100)
631
+
632
+ final_bal = curr_cap + pnl_val
633
+ wallet_color = "#00ff00" if pnl_val >= 0 else "#ff0000"
634
+
635
+ # 3. 🎨 Wallet HTML (مع توقيتات بايثون)
636
+ wallet_md = f"""
637
+ <div style='background-color: #1a1a1a; padding: 15px; border-radius: 8px; border: 1px solid #333; text-align:center;'>
638
+ <h3 style='margin:0; color:#888; font-size:14px;'>💰 Live Wallet</h3>
639
+ <div style='font-size: 26px; font-weight: bold; color: white; margin: 5px 0;'>
640
+ ${final_bal:,.2f}
641
+ </div>
642
+ <div style='font-size: 14px; color: {wallet_color};'>
643
+ ({pnl_val:+,.2f} USD)
644
+ </div>
645
+ <hr style='border-color:#444; margin: 10px 0;'>
646
 
647
+ <div style='display: flex; justify-content: space-between; font-size: 12px; color: #ccc;'>
648
+ <span>⏳ Uptime:</span>
649
+ <span style='color: white; font-family: monospace;'>{uptime_str}</span>
650
+ </div>
651
+ <div style='display: flex; justify-content: space-between; font-size: 12px; color: #ccc; margin-top:5px;'>
652
+ <span>⏱️ Trade Time:</span>
653
+ <span style='color: #00e5ff; font-family: monospace;'>{trade_dur_str}</span>
654
+ </div>
655
+ </div>
656
+ """
657
+
658
+ # 4. 🧠 [Dynamic Stats] جلب الإحصائيات بناءً على القائمة المنسدلة
659
+ key_map = {
660
+ "Hybrid System": "hybrid",
661
+ "Model V2 (Radar)": "v2",
662
+ "Model V3 (Sniper)": "v3"
663
+ }
664
+ target_key = key_map.get(selected_view, "hybrid")
665
+
666
+ # Get stats safely
667
+ if hasattr(trade_manager, 'ai_stats'):
668
+ stats_data = trade_manager.ai_stats.get(target_key, {"total":0, "good":0, "saved":0.0, "missed":0.0})
669
+ else:
670
+ stats_data = {"total":0, "good":0, "saved":0.0, "missed":0.0}
671
 
672
+ tot_ds = stats_data['total']
673
+ ds_acc = (stats_data['good'] / tot_ds * 100) if tot_ds > 0 else 0.0
674
+
675
+ # Dynamic Title based on selection
676
+ title_map = {
677
+ "hybrid": "🧠 Hybrid Guardian IQ",
678
+ "v2": "📡 Model V2 (Radar)",
679
+ "v3": "🎯 Model V3 (Sniper)"
680
+ }
681
+ stats_title = title_map.get(target_key, "DeepSteward IQ")
682
+
683
+ history_md = f"""
684
+ <div style='background-color: #1a1a1a; padding: 10px; border-radius: 8px; border: 1px solid #333; font-size: 12px;'>
685
+ <h3 style='margin:0 0 5px 0; color:#888; font-size:14px;'>📊 Performance</h3>
686
+ <table style='width:100%; color:white; border-collapse: collapse;'>
687
+ <tr><td style='padding:2px;'>Total Trades:</td><td style='text-align:right; font-weight:bold;'>{total_t}</td></tr>
688
+ <tr><td style='padding:2px;'>Win Rate:</td><td style='text-align:right; color:{"#00ff00" if win_rate>=50 else "#ff0000"};'>{win_rate:.1f}%</td></tr>
689
+ <tr><td style='padding:2px;'>Wins:</td><td style='text-align:right; color:#00ff00;'>{wins} (+${tot_prof:,.2f})</td></tr>
690
+ <tr><td style='padding:2px;'>Losses:</td><td style='text-align:right; color:#ff0000;'>{losses} (-${tot_loss:,.2f})</td></tr>
691
+ <tr><td style='border-top:1px solid #444; padding-top:5px;'>Net Profit:</td><td style='border-top:1px solid #444; text-align:right; padding-top:5px; color:{"#00ff00" if net_prof>=0 else "#ff0000"};'>${net_prof:,.2f}</td></tr>
692
+ </table>
693
 
694
+ <hr style='border-color:#444; margin: 8px 0;'>
 
695
 
696
+ <h3 style='margin:0 0 5px 0; color: #00e5ff; font-size:14px;'>{stats_title}</h3>
697
+ <table style='width:100%; color:white;'>
698
+ <tr><td>Exits:</td><td style='text-align:right;'>{tot_ds}</td></tr>
699
+ <tr><td>Accuracy:</td><td style='text-align:right; color:#00e5ff;'>{ds_acc:.1f}%</td></tr>
700
+ <tr><td>Saved Loss:</td><td style='text-align:right; color:#00ff00;'>${stats_data['saved']:.2f}</td></tr>
701
+ <tr><td>Missed Profit:</td><td style='text-align:right; color:#ff0000;'>${stats_data['missed']:.2f}</td></tr>
702
+ </table>
703
+ </div>
704
+ """
705
+
706
+ # 5. Chart Logic (gr.Plot - No HTML)
707
+ fig = empty_chart
708
+ if symbol and curr_p > 0:
709
+ ohlcv = await data_manager.get_latest_ohlcv(symbol, '5m', 120)
710
+ if ohlcv and len(ohlcv) > 0:
711
+ df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
712
+ df['datetime'] = pd.to_datetime(df['timestamp'], unit='ms')
713
+
714
+ fig = go.Figure(data=[go.Candlestick(
715
+ x=df['datetime'],
716
+ open=df['open'], high=df['high'], low=df['low'], close=df['close'],
717
+ increasing_line_color='#00ff00', decreasing_line_color='#ff0000',
718
+ name=symbol
719
+ )])
720
+
721
+ # Lines
722
+ fig.add_hline(y=entry_p, line_dash="dash", line_color="white", opacity=0.5, annotation_text="Entry")
723
+ fig.add_hline(y=tp_p, line_color="#00ff00", line_width=1, annotation_text="TP")
724
+ fig.add_hline(y=sl_p, line_color="#ff0000", line_width=1, annotation_text="SL")
725
+
726
+ fig.update_layout(
727
+ template="plotly_dark",
728
+ paper_bgcolor="#0b0f19",
729
+ plot_bgcolor="#0b0f19",
730
+ margin=dict(l=0, r=50, t=30, b=0),
731
+ height=400, # ارتفاع مناسب
732
+ xaxis_rangeslider_visible=False,
733
+ title=dict(text=f"{symbol} (5m)", x=0.05, font=dict(color="white", size=14)),
734
+ yaxis=dict(side='right', gridcolor='#222')
735
+ )
736
+
737
+ # 6. Watchlist Dataframe
738
+ wl_data = [[k, f"{v.get('final_total_score',0):.2f}"] for k, v in trade_manager.watchlist.items()]
739
+ wl_df = pd.DataFrame(wl_data, columns=["Coin", "Score"])
740
+
741
+ status_txt = sys_state.last_cycle_logs
742
+ status_line = f"Status: {'READY' if not sys_state.cycle_running else 'SCANNING...'} | Auto-Pilot: {'ON' if sys_state.auto_pilot else 'OFF'}"
743
+
744
+ # Return individual items to prevent flicker
745
+ return (
746
+ status_txt,
747
+ status_line,
748
+ fig,
749
+ f"{curr_p:.6f}",
750
+ f"{entry_p:.6f}",
751
+ f"{tp_p:.6f}",
752
+ f"{sl_p:.6f}",
753
+ f"{pnl_pct:+.2f}%", # PnL Percent
754
+ wl_df,
755
+ wallet_md,
756
+ history_md
757
+ )
758
+
759
+ except Exception:
760
+ traceback.print_exc()
761
+ return "Error", "Error", empty_chart, "0", "0", "0", "0", "0%", wl_df_empty, "Err", "Err"
762
+
763
+ async def run_cycle_from_gradio():
764
+ if sys_state.cycle_running: return "الدورة تعمل بالفعل."
765
+ await run_unified_cycle()
766
+ return sys_state.last_cycle_logs
767
+
768
+ # ==============================================================================
769
+ # 🚀 بناء وتثبيت واجهة Gradio (Final Layout)
770
+ # ==============================================================================
771
+ def create_gradio_ui():
772
+ css = """
773
+ .gradio-container { background-color: #0b0f19; }
774
+ .dataframe { color: white !important; background-color: #1a1a1a !important; }
775
+ thead th { background-color: #333 !important; color: white !important; }
776
+ /* Fix height for HTML boxes to prevent layout jump */
777
+ .html-box { min-height: 180px; }
778
+ """
779
+
780
+ with gr.Blocks(title="Titan V22.5 Pro Dashboard", css=css, theme=gr.themes.Monochrome()) as demo:
781
+
782
+ gr.Markdown("# 🚀 Titan V22.5 Pro (Hybrid Guardian Edition)")
783
+
784
+ # --- الصف العلوي: الشارت + الإحصائيات ---
785
+ with gr.Row():
786
+ # العمود الأيسر: الشارت + تفاصيل السعر (لتقليل الوميض نفصلها)
787
+ with gr.Column(scale=3):
788
+ # 🌟 استخدام gr.Plot هو الحل الجذري لمشكلة الشارت
789
+ live_chart_plot = gr.Plot(label="Live Chart", container=True)
790
+
791
+ # صف التفاصيل الحية (تحديث نصوص فقط = لا وميض)
792
+ with gr.Row():
793
+ t_price = gr.Textbox(label="Current Price", interactive=False, elem_classes="text-yellow")
794
+ t_pnl = gr.Textbox(label="PnL %", interactive=False)
795
+
796
+ with gr.Row():
797
+ t_entry = gr.Textbox(label="Entry", interactive=False)
798
+ t_tp = gr.Textbox(label="Target (TP)", interactive=False, elem_classes="text-green")
799
+ t_sl = gr.Textbox(label="Stop (SL)", interactive=False, elem_classes="text-red")
800
+
801
+ # العمود الأيمن: المحفظة والإحصائيات
802
+ with gr.Column(scale=1):
803
+ wallet_output = gr.HTML(label="Wallet", elem_classes="html-box")
804
+
805
+ # [جديد] قائمة منسدلة لاختيار عرض الإحصائيات
806
+ stats_dropdown = gr.Dropdown(
807
+ choices=["Hybrid System", "Model V2 (Radar)", "Model V3 (Sniper)"],
808
+ value="Hybrid System",
809
+ label="Select AI View",
810
+ interactive=True
811
+ )
812
+
813
+ history_output = gr.HTML(label="Stats", elem_classes="html-box")
814
+ watchlist_output = gr.DataFrame(label="Watchlist", interactive=False)
815
+
816
+ gr.HTML("<hr style='border-color: #333;'>")
817
+
818
+ # --- الصف السفلي: التحكم والسجلات ---
819
+ with gr.Row():
820
+ with gr.Column(scale=1):
821
+ gr.Markdown("## 🎮 Controls")
822
+
823
+ auto_pilot_checkbox = gr.Checkbox(
824
+ label="✈️ Auto-Pilot",
825
+ value=True
826
+ )
827
+
828
+ with gr.Row():
829
+ run_cycle_btn = gr.Button("🚀 Scan Now", variant="primary")
830
+ close_trade_btn = gr.Button("🚨 Panic Close", variant="stop")
831
+ reset_stats_btn = gr.Button("🗑️ Reset Stats", variant="secondary")
832
+
833
+ status_markdown = gr.Markdown("Initializing...")
834
+ alert_box = gr.Textbox(label="Alerts", interactive=False, visible=True)
835
+
836
+ with gr.Column(scale=3):
837
+ gr.Markdown("## 📜 System Logs")
838
+ cycle_logs_output = gr.Textbox(
839
+ lines=10,
840
+ autoscroll=True,
841
+ max_lines=50,
842
+ value="..."
843
+ )
844
+
845
+ # --- التفاعلات (Interactions) ---
846
+
847
+ run_cycle_btn.click(fn=run_cycle_from_gradio, inputs=None, outputs=cycle_logs_output)
848
+ close_trade_btn.click(fn=manual_close_current_trade, inputs=None, outputs=alert_box)
849
+ reset_stats_btn.click(fn=reset_stats_handler, inputs=None, outputs=alert_box)
850
+ auto_pilot_checkbox.change(fn=toggle_auto_pilot, inputs=auto_pilot_checkbox, outputs=alert_box)
851
+
852
+ # المؤقت الدوري (كل 3 ثواني) - تم ربط القائمة المنسدلة كـ Input
853
+ timer = gr.Timer(3)
854
+ timer.tick(
855
+ fn=check_live_pnl_and_status,
856
+ inputs=[stats_dropdown], # نمرر قيمة القائمة المنسدلة هنا
857
+ outputs=[
858
+ cycle_logs_output,
859
+ status_markdown,
860
+ live_chart_plot,
861
+ t_price,
862
+ t_entry,
863
+ t_tp,
864
+ t_sl,
865
+ t_pnl,
866
+ watchlist_output,
867
+ wallet_output,
868
+ history_output
869
+ ]
870
+ )
871
+
872
+ return demo
873
+
874
+ gradio_dashboard = create_gradio_ui()
875
+ app = gr.mount_gradio_app(app, gradio_dashboard, path="/")
876
+
877
+ # ==============================================================================
878
+ # 🏁 تشغيل الخادم
879
+ # ==============================================================================
880
+ if __name__ == "__main__":
881
+ import uvicorn
882
+ port = int(os.environ.get("PORT", 7860))
883
+ uvicorn.run(app, host="0.0.0.0", port=port)