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

Update trade_manager.py

Browse files
Files changed (1) hide show
  1. trade_manager.py +473 -839
trade_manager.py CHANGED
@@ -1,883 +1,517 @@
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)
 
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.")