Riy777 commited on
Commit
bd24c07
·
1 Parent(s): ae323ae

Update trade_manager.py

Browse files
Files changed (1) hide show
  1. trade_manager.py +333 -258
trade_manager.py CHANGED
@@ -1,17 +1,31 @@
1
- # trade_manager.py (V15.1 - Full Original Production Version with Guard V2)
2
  import asyncio
3
  import json
4
  import uuid
5
  import traceback
6
  from datetime import datetime
7
 
 
 
 
 
 
 
 
 
 
8
  class TradeManager:
9
- def __init__(self, r2_service, data_manager, titan_engine, pattern_engine, guard_engine=None): # <--- [MODIFIED] إضافة guard_engine
 
 
10
  self.r2 = r2_service
11
  self.data_manager = data_manager
12
  self.titan = titan_engine
13
  self.pattern_engine = pattern_engine
14
- self.guard = guard_engine # <--- [NEW] تخزين محرك الحماية
 
 
 
15
 
16
  # قواميس تتبع الحالة في الذاكرة (In-Memory State)
17
  self.open_positions = {}
@@ -20,10 +34,20 @@ class TradeManager:
20
  self.running = True
21
 
22
  # 🔒 قفل لضمان عدم تضارب عمليات الشراء/البيع المتزامنة
23
- # هذا القفل يمنع حالتين من الدخول إلى منطقة التعديل على المحفظة في نفس الوقت
24
  self.execution_lock = asyncio.Lock()
25
 
26
- print("🛡️ [TradeManager V15.1] Sentry Module Initialized (Guard V2 Ready).")
 
 
 
 
 
 
 
 
 
 
 
27
 
28
  async def initialize_sentry_exchanges(self):
29
  """
@@ -31,307 +55,358 @@ class TradeManager:
31
  تعتمد الآن على وظيفة المزامنة المركزية.
32
  """
33
  print("🛡️ [TradeManager] Initializing and syncing state with R2...")
34
- await self.sync_internal_state()
35
 
36
- async def sync_internal_state(self):
37
  """
38
- مزامنة الحالة الداخلية مع R2 لضمان عدم فقدان أي صفقة مفتوحة.
39
- تستخدم عند بدء النظام وعند بدء كل دورة تحليل جديدة لضمان تطابق البيانات.
40
  """
41
  try:
42
- # جلب الصفقات المسجلة في R2
43
- loaded_trades = await self.r2.get_open_trades_async()
44
-
45
- if loaded_trades:
46
- # إنشاء مجموعة من الرموز الموجودة في R2 للمقارنة
47
- r2_symbols = set(t['symbol'] for t in loaded_trades)
48
-
49
- # 1. إضافة الصفقات المفقودة من الذاكرة
50
- for t in loaded_trades:
51
- symbol = t['symbol']
52
- if symbol not in self.open_positions:
53
- self.open_positions[symbol] = t
54
- print(f"🔄 [TradeManager] SYNC: Resumed watching trade from R2: {symbol}")
55
-
56
- # 2. (اختياري) تنظيف الذاكرة من الصفقات التي أغلقت في R2 ولكنها علقت هنا
57
- # يتم ذلك بمقارنة عكسية إذا تطلب الأمر مستقبلاً
58
-
59
- # التأكد من أن جميع الصفقات المفتوحة لديها حراس يعملون
60
- await self.start_sentry_loops()
61
-
62
  except Exception as e:
63
- print(f"⚠️ [TradeManager] Critical State Sync Warning: {e}")
64
- traceback.print_exc()
65
 
66
- async def update_sentry_watchlist(self, approved_candidates):
 
 
 
 
67
  """
68
- تحديث قائمة المراقبة النشطة للحارس (Sniper) بناءً على ترشيحات الدماغ الجديدة.
 
69
  """
70
- # إيقاف المهام القديمة التي لم تعد في القائمة الجديدة (تنظيف)
71
- new_symbols = set(c['symbol'] for c in approved_candidates)
72
- for symbol in list(self.watchlist.keys()):
73
- if symbol not in new_symbols:
74
- if symbol in self.sentry_tasks:
75
- self.sentry_tasks[symbol].cancel()
76
- del self.watchlist[symbol]
77
-
78
- # إضافة المرشحين الجدد
79
- for cand in approved_candidates:
80
- symbol = cand['symbol']
81
- if symbol not in self.watchlist:
82
- self.watchlist[symbol] = {
83
- 'data': cand,
84
- 'added_at': datetime.utcnow().isoformat(),
85
- 'monitoring_started': False
86
- }
87
-
88
- print(f"📋 [Sniper] Watchlist updated. Active targets: {len(self.watchlist)}")
89
-
90
- # بدء مهام المراقبة الجديدة فوراً
91
- for symbol in list(self.watchlist.keys()):
92
- # نتأكد من عدم وجود مهمة قائمة بالفعل لنفس الرمز
93
- if symbol not in self.sentry_tasks or self.sentry_tasks[symbol].done():
94
- print(f"🎯 [Sniper] Locking radar on target: {symbol}")
95
- self.sentry_tasks[symbol] = asyncio.create_task(self._sniper_loop(symbol))
96
-
97
- async def _sniper_loop(self, symbol):
98
- """
99
- حلقة المراقبة التكتيكية (القناص) - تراقب الرمز لحظة بلحظة لاقتناص فرصة الدخول.
100
- """
101
- print(f"👁️ [Sniper] Monitoring {symbol} for tactical entry...")
102
- consecutive_signals = 0
103
- required_signals = 2 # عدد الشموع المتتالية المطلوبة لتأكيد الزخم
104
 
 
105
  try:
106
- while self.running and symbol in self.watchlist and symbol not in self.open_positions:
107
- # 1. فحص سريع: هل وصلنا للحد الأقصى للصفقات؟
108
- # إذا نعم، نهدئ اللعب لتوفير الموارد
109
- if len(self.open_positions) >= 1:
110
- await asyncio.sleep(60) # انتظار دقيقة كاملة قبل الفحص التالي
111
- continue
112
-
113
- # 2. جلب بيانات الشموع اللحظية (5 دقائق)
114
- ohlcv = await self.data_manager.get_latest_ohlcv(symbol, timeframe='5m', limit=30)
115
- if not ohlcv or len(ohlcv) < 20:
116
- await asyncio.sleep(30)
117
- continue
118
-
119
- # 3. تحليل فوري باستخدام محرك تيتان
120
- titan_res = self.titan.predict({'5m': ohlcv})
121
- current_score = titan_res.get('score', 0.0)
122
-
123
- # 4. تقييم الإشارة
124
- is_signal = False
125
- entry_reason = ""
126
 
127
- # شرط الدخول: زخم قوي جداً (فوق 0.80)
128
- if current_score > 0.80:
129
- is_signal = True
130
- entry_reason = f"Titan Surge ({current_score:.2f})"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
- if is_signal:
133
- consecutive_signals += 1
134
- print(f"🔥 [Sniper] {symbol} Signal detected! ({consecutive_signals}/{required_signals}) - {entry_reason}")
135
-
136
- if consecutive_signals >= required_signals:
137
- # محاولة تنفيذ الشراء
138
- current_price = ohlcv[-1][4] # آخر سعر إغلاق
139
- # نستخدم await هنا لأن الدالة execute_buy_order تحتوي على قفل async
140
- executed = await self.execute_buy_order(symbol, entry_reason, current_price)
141
 
142
- if executed:
143
- # تم الشراء بنجاح، ننهي مهمة القناص هذه
144
- print(f"✅ [Sniper] Mission accomplished for {symbol}. Standing down.")
145
- break
146
- else:
147
- # فشل الشراء (ربما بسبب اكتمال العدد أو نقص الرصيد)، نعيد المحاولة لاحقاً
148
- consecutive_signals = 0
149
- else:
150
- # إذا انقطعت سلسلة الإشارات، نعيد العداد للصفر
151
- if consecutive_signals > 0:
152
- # print(f"❄️ [Sniper] {symbol} signal cooled off.")
153
- consecutive_signals = 0
154
-
155
- await asyncio.sleep(30) # فاصل زمني 30 ثانية بين كل فحص
156
-
157
- except asyncio.CancelledError:
158
- # تم إلغاء المهمة يدوياً (مثلاً عند تحديث القائمة)
159
- pass
160
  except Exception as e:
161
- print(f"❌ [Sniper Error] Loop crashed for {symbol}: {e}")
162
  traceback.print_exc()
163
 
164
- async def execute_buy_order(self, symbol, reason, price):
 
 
 
 
165
  """
166
- تنفيذ أمر الشراء. هذه الدالة حرجة جداً ومحمية بقفل (Lock).
 
167
  """
168
- async with self.execution_lock:
169
- # --- منطقة حرجة (Critical Section) ---
170
- # لا يمكن لأي كود آخر تعديل الصفقات أو المحفظة أثناء وجودنا هنا
171
-
172
- # 1. التحقق النهائي من عدد الصفقات
173
- if len(self.open_positions) >= 1:
174
- print(f"⚠️ [Execution Blocked] Cannot buy {symbol}. Max positions (1) reached.")
175
- return False
176
-
177
- # 2. جلب وتحديث المحفظة
178
- portfolio = await self.r2.get_portfolio_state_async()
179
- capital = portfolio.get('current_capital_usd', 0.0)
180
-
181
- if capital < 5.0: # حد أدنى للرصيد (مثلاً 5 دولار)
182
- print(f"⚠️ [Execution Failed] Insufficient capital for {symbol}: ${capital:.2f}")
183
- return False
184
 
185
- # 3. حساب كمية الاستثمار
186
- invest_amount = capital * 0.99 # استثمار 99% من الرصيد المتاح
187
- quantity = invest_amount / price
 
 
 
 
188
 
189
- # 4. إنشاء سجل الصفقة
190
- trade_id = str(uuid.uuid4())[:8]
 
 
 
191
  new_trade = {
192
- 'trade_id': trade_id,
193
  'symbol': symbol,
 
 
194
  'status': 'OPEN',
195
- 'entry_time': datetime.utcnow().isoformat(),
196
- 'entry_price': price,
197
- 'quantity': quantity,
198
- 'invested_usd': invest_amount,
199
- 'entry_reason': reason,
200
- 'tp_price': price * 1.05, # هدف أولي +5%
201
- 'sl_price': price * 0.95, # وقف أولي -5%
202
- 'highest_price': price # لتتبع الوقف المتحرك
 
 
 
203
  }
204
 
205
- # 5. تحديث الحالة الداخلية
 
 
 
 
 
 
 
206
  self.open_positions[symbol] = new_trade
 
 
207
  if symbol in self.watchlist:
208
  del self.watchlist[symbol]
209
-
210
- # 6. تحديث وحفظ البيانات في R2 (دفعة واحدة لضمان التناسق)
211
- portfolio['current_capital_usd'] -= invest_amount
212
- portfolio['invested_capital_usd'] += invest_amount
213
- portfolio['total_trades'] += 1
214
 
215
- await self.r2.save_portfolio_state_async(portfolio)
216
  await self.r2.save_open_trades_async(list(self.open_positions.values()))
217
 
218
- print(f"🚀 [EXECUTED] BUY {symbol} @ {price:.4f} | Invested: ${invest_amount:.2f} | Reason: {reason}")
 
 
219
 
220
- # 7. تشغيل الحارس فوراً
221
- if symbol in self.sentry_tasks and not self.sentry_tasks[symbol].done():
222
- self.sentry_tasks[symbol].cancel() # إلغاء مهمة القناص القديمة إن وجدت
223
  self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
224
 
225
- return True
 
 
 
 
 
226
 
227
- async def _guardian_loop(self, symbol):
 
 
 
228
  """
229
- حلقة الحماية (Guardian) - تراقب الصفقة المفتوحة وتدير الخروج.
230
- [MODIFIED V15.1] الآن مدمج بها منطق Guard V2 للخروج الذكي.
231
  """
232
- print(f"🛡️ [Guardian V2] Activated for position: {symbol}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
 
234
- # متغير لتتبع آخر مرة تم فيها فحص الحارس الذكي لتخفيف الضغط
235
- last_guard_check = 0
236
- GUARD_CHECK_INTERVAL = 60 # فحص ذكي كل 60 ثانية (مناسب لشمعة 5 دقائق)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
 
238
- try:
239
- while self.running and symbol in self.open_positions:
240
- trade = self.open_positions[symbol]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
 
242
- # جلب السعر الحالي
 
 
 
 
243
  current_price = await self.data_manager.get_latest_price_async(symbol)
244
- if not current_price or current_price <= 0:
245
- await asyncio.sleep(10)
246
- continue
247
 
248
- # تحديث أعلى سعر وصل له (مهم للوقف المتحرك المستقبلي)
249
- if current_price > trade.get('highest_price', 0):
250
- trade['highest_price'] = current_price
251
- # ملاحظة: لا نحفظ في R2 هنا لتجنب كثرة الكتابة، نعتمد على الذاكرة
 
 
252
 
253
- # --- 1. خطوط الدفاع الكلاسيكية (Hard Stops) ---
254
  if current_price <= trade['sl_price']:
255
- print(f"🛡️ [Guardian] STOP LOSS triggered for {symbol} @ {current_price:.4f} (SL: {trade['sl_price']:.4f})")
256
- await self.execute_sell_order(symbol, "Hard Stop Loss Hit", current_price)
257
- break
258
-
259
- elif current_price >= trade['tp_price']:
260
- print(f"💰 [Guardian] TAKE PROFIT triggered for {symbol} @ {current_price:.4f} (TP: {trade['tp_price']:.4f})")
261
- await self.execute_sell_order(symbol, "Target Take Profit Hit", current_price)
262
- break
263
-
264
- # --- 2. خط الدفاع الذكي (Guard V2 AI) --- <--- [NEW SECTION]
265
- now_ts = asyncio.get_event_loop().time()
266
- if self.guard and self.guard.initialized and (now_ts - last_guard_check > GUARD_CHECK_INTERVAL):
267
- last_guard_check = now_ts
268
- # نحتاج لبيانات كافية للمؤشرات (300 شمعة لتكون آمنة)
269
- ohlcv_5m = await self.data_manager.get_latest_ohlcv(symbol, timeframe='5m', limit=300)
270
 
271
- if ohlcv_5m and len(ohlcv_5m) >= 250:
272
- # استدعاء الحارس في خيط منفصل (Thread) لأنه عملية حسابية ثقيلة
273
- guard_decision = await asyncio.to_thread(self.guard.check_exit_signal, symbol, ohlcv_5m)
274
 
275
- if guard_decision['action'] == 'EXIT_NOW':
276
- print(f"🚨 [Guard V2] AI EMERGENCY EXIT for {symbol}! {guard_decision['reason']}")
277
- # تنفيذ خروج فوري بالسعر الحالي
278
- await self.execute_sell_order(symbol, "Guard V2 AI Exit", current_price)
279
- break
280
- # else:
281
- # # يمكن تفعيل هذا السطر للتأكد من عمل الحارس في الـ logs
282
- # print(f" 🛡️ [Guard V2] Monitoring {symbol}... Status: SECURE (Conf: {guard_decision.get('confidence',0):.2f})")
283
-
284
- await asyncio.sleep(10) # مراقبة نشطة للسعر كل 10 ثواني
285
-
286
- except asyncio.CancelledError:
287
- print(f"🛡️ [Guardian] Task cancelled for {symbol}")
288
- except Exception as e:
289
- print(f"❌ [Guardian Error] Loop crashed for {symbol}: {e}")
290
- traceback.print_exc()
291
 
292
- async def execute_sell_order(self, symbol, reason, price):
 
 
 
 
 
 
 
 
 
 
 
 
293
  """
294
- تنفيذ أمر البيع. محمي بـ execution_lock.
 
295
  """
296
- async with self.execution_lock:
297
- # --- منطقة حرجة ---
298
- if symbol not in self.open_positions:
299
- print(f"⚠️ [Sell Failed] {symbol} not found in open positions during execution.")
300
- return
 
 
 
 
 
 
 
 
301
 
302
- trade = self.open_positions.pop(symbol)
 
 
 
 
303
 
304
- # حسابات الأرباح
305
- revenue = trade['quantity'] * price
306
- profit_usd = revenue - trade['invested_usd']
307
- profit_pct = (profit_usd / trade['invested_usd']) * 100
308
-
309
- # تح��يث المحفظة
310
- portfolio = await self.r2.get_portfolio_state_async()
311
- portfolio['current_capital_usd'] += revenue
312
- portfolio['invested_capital_usd'] -= trade['invested_usd']
313
- portfolio['total_profit_usd'] += profit_usd
314
 
315
- if profit_usd > 0:
316
- portfolio['winning_trades'] += 1
317
 
318
- # حفظ التحديثات النهائية في R2
319
- await self.r2.save_portfolio_state_async(portfolio)
320
  await self.r2.save_open_trades_async(list(self.open_positions.values()))
 
 
321
 
322
- print(f"💰 [SOLD] {symbol} @ {price:.4f} | PnL: ${profit_usd:.2f} ({profit_pct:+.2f}%) | Reason: {reason}")
323
-
324
- async def execute_emergency_exit(self, symbol, reason):
325
- """واجهة للخروج الطارئ اليدوي أو من الدماغ"""
326
- print(f"🚨 [Emergency] Requested exit for {symbol} due to: {reason}")
327
- current_price = await self.data_manager.get_latest_price_async(symbol)
328
- if current_price and current_price > 0:
329
- await self.execute_sell_order(symbol, f"EMERGENCY: {reason}", current_price)
330
- else:
331
- print(f"❌ [Emergency Failed] Could not fetch valid price for {symbol}")
332
 
333
- async def update_trade_targets(self, symbol, new_tp, new_sl, reason):
334
- """تحديث أهداف الصفقة بناءً على أوامر الدماغ"""
 
 
 
 
 
 
 
 
 
 
335
  if symbol in self.open_positions:
336
  trade = self.open_positions[symbol]
337
  old_tp = trade['tp_price']
@@ -344,18 +419,20 @@ class TradeManager:
344
  # حفظ فوري لضمان عدم ضياع الأهداف الجديدة في حال إعادة التشغيل
345
  await self.r2.save_open_trades_async(list(self.open_positions.values()))
346
 
347
- print(f"🎯 [Targets Updated] {symbol} | TP: {old_tp:.4f}->{trade['tp_price']:.4f} | SL: {old_sl:.4f}->{trade['sl_price']:.4f} | Reason: {reason}")
348
  else:
349
- print(f"⚠️ [Target Update Failed] {symbol} is not currently open.")
350
 
 
351
  async def start_sentry_loops(self):
352
  """بدء أو استئناف جميع مهام الحراسة"""
353
  for symbol in list(self.open_positions.keys()):
354
  # إذا لم يكن هناك حارس نشط، نعين واحداً
355
  if symbol not in self.sentry_tasks or self.sentry_tasks[symbol].done():
356
- print(f"🛡️ [Sentry Restart] Activating guardian for existing trade: {symbol}")
357
  self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
358
 
 
359
  async def stop_sentry_loops(self):
360
  """إيقاف نظيف لجميع المهام الخلفية"""
361
  print("🛑 [TradeManager] Stopping all sentry loops...")
@@ -363,8 +440,6 @@ class TradeManager:
363
  for sym, task in self.sentry_tasks.items():
364
  task.cancel()
365
 
366
- # انتظار انتهاء المهام (اختياري لكن أفضل للأمان)
367
- if self.sentry_tasks:
368
- await asyncio.gather(*self.sentry_tasks.values(), return_exceptions=True)
369
-
370
- print("✅ [TradeManager] All loops stopped.")
 
1
+ # trade_manager.py (V15.2 - Full Production - Sniper V3 + Guard V2 Integrated)
2
  import asyncio
3
  import json
4
  import uuid
5
  import traceback
6
  from datetime import datetime
7
 
8
+ # (لا نحتاج للاستيراد هنا، سيتم تمريرها عبر __init__)
9
+ # from r2 import R2Service
10
+ # from data_manager import DataManager
11
+ # from ml_engine.titan_engine import TitanEngine
12
+ # from ml_engine.patterns import ChartPatternAnalyzer
13
+ # from ml_engine.guard_engine import GuardEngine
14
+ # from ml_engine.sniper_engine import SniperEngine
15
+
16
+
17
  class TradeManager:
18
+ # [ 🚀 🚀 🚀 ]
19
+ # [ 💡 💡 💡 ] التعديل: إضافة sniper_engine إلى __init__
20
+ def __init__(self, r2_service, data_manager, titan_engine, pattern_engine, guard_engine=None, sniper_engine=None):
21
  self.r2 = r2_service
22
  self.data_manager = data_manager
23
  self.titan = titan_engine
24
  self.pattern_engine = pattern_engine
25
+ self.guard = guard_engine # (هذا هو حارس الخروج V1.0)
26
+ self.sniper = sniper_engine # (هذا هو قناص الدخول V3.0)
27
+
28
+ # [ 🚀 🚀 🚀 ]
29
 
30
  # قواميس تتبع الحالة في الذاكرة (In-Memory State)
31
  self.open_positions = {}
 
34
  self.running = True
35
 
36
  # 🔒 قفل لضمان عدم تضارب عمليات الشراء/البيع المتزامنة
 
37
  self.execution_lock = asyncio.Lock()
38
 
39
+ print(f"🛡️ [TradeManager V15.2] Sentry Module Initialized.")
40
+ # (طباعة حالة كلا المحركين)
41
+ if self.guard and self.guard.initialized:
42
+ print(f" -> Guard V2 (Exit Protector) Ready. Threshold: {self.guard.EXIT_THRESHOLD}")
43
+ else:
44
+ print(" -> ⚠️ Guard V2 (Exit Protector) NOT INITIALIZED.")
45
+
46
+ if self.sniper and self.sniper.initialized:
47
+ print(f" -> 🎯 Sniper V3 (Entry Sniper) Ready. Threshold: {self.sniper.threshold}")
48
+ else:
49
+ print(" -> ⚠️ Sniper V3 (Entry Sniper) NOT INITIALIZED.")
50
+
51
 
52
  async def initialize_sentry_exchanges(self):
53
  """
 
55
  تعتمد الآن على وظيفة المزامنة المركزية.
56
  """
57
  print("🛡️ [TradeManager] Initializing and syncing state with R2...")
58
+ await self.sync_internal_state_with_r2()
59
 
60
+ async def sync_internal_state_with_r2(self):
61
  """
62
+ مزامنة الحالة الداخلية (الصفقات المفتوحة) مع R2.
 
63
  """
64
  try:
65
+ open_trades_list = await self.r2.load_open_trades_async()
66
+ self.open_positions = {trade['symbol']: trade for trade in open_trades_list}
67
+ print(f" -> [Sync] تم استعادة {len(self.open_positions)} صفقة مفتوحة من R2.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  except Exception as e:
69
+ print(f" [TradeManager] فشل فادح في مزامنة R2 (load_open_trades_async): {e}")
70
+ self.open_positions = {} # البدء بحالة نظيفة إذا فشلت المزامنة
71
 
72
+ # ==============================================================================
73
+ # 🎯 [ 🚀 🚀 🚀 ]
74
+ # [ 💡 💡 💡 ] التعديل: دمج القناص (Sniper L2) هنا
75
+ # ==============================================================================
76
+ async def _handle_new_signal(self, symbol, signal_data):
77
  """
78
+ (V15.2) معالج إشارات L1.
79
+ الآن يقوم باستدعاء القناص (L2 Sniper) للتأكيد قبل التنفيذ.
80
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
+ # --- 1. التحقق من الشروط المسبقة (من ملفك الأصلي) ---
83
  try:
84
+ # (التحقق من أن الإشارة تحتوي على البيانات اللازمة)
85
+ if 'enhanced_final_score' not in signal_data or 'current_price' not in signal_data:
86
+ print(f"⚠️ [Signal Skip] {symbol} الإشارة تفتقد بيانات أساسية.")
87
+ return
88
+
89
+ l1_score = signal_data['enhanced_final_score']
90
+
91
+ # لتحقق من عتبة L1 - يتم هذا في app.py الآن)
92
+ # if l1_score < self.data_manager.HYBRID_ENTRY_THRESHOLD:
93
+ # return # (تجاهل الإشارات الضعيفة بصمت)
94
+
95
+ # (التحقق من أن الصفقة ليست مفتوحة أو في قائمة المراقبة بالفعل)
96
+ if symbol in self.open_positions or symbol in self.watchlist:
97
+ # print(f" -> [L1 Skip] {symbol} الصفقة مفتوحة بالفعل أو قيد المر��قبة.")
98
+ return
 
 
 
 
 
99
 
100
+ # (تم تجاوز الفحص الأولي، طباعة)
101
+ print(f" -> [L1 OK] {symbol} (Score: {l1_score:.2f}) > {self.data_manager.HYBRID_ENTRY_THRESHOLD}.")
102
+
103
+ # --- 2. التحقق من القناص (L2 Sniper) ---
104
+
105
+ # (التحقق من وجود القناص وتهيئة)
106
+ if not self.sniper or not self.sniper.initialized:
107
+ print(f"⚠️ [TradeManager] SniperEngine V3 غير جاهز لـ {symbol}. تنفيذ الدخول مباشرة (L1-Only).")
108
+ # (العودة إلى السلوك القديم: التنفيذ الفوري)
109
+ async with self.execution_lock:
110
+ if symbol in self.open_positions: return # (تحقق مزدوج)
111
+ await self._execute_entry_from_signal(symbol, signal_data)
112
+ return
113
+
114
+ # (استدعاء القناص)
115
+ print(f" -> [L2 Check] {symbol} التحقق من L2 (Sniper)...")
116
+ # (القناص يحتاج لبيانات 1m كافية لحساب الميزات)
117
+ ohlcv_1m = await self.data_manager.get_latest_ohlcv(symbol, '1m', 600)
118
+
119
+ if not ohlcv_1m or len(ohlcv_1m) < self.sniper.LOOKBACK_WINDOW:
120
+ print(f" -> [L2 Skip] {symbol} بيانات 1m غير كافية للقناص ({len(ohlcv_1m)} < {self.sniper.LOOKBACK_WINDOW}).")
121
+ return
122
+
123
+ sniper_result = await self.sniper.check_entry_signal_async(ohlcv_1m)
124
+
125
+ # --- 3. قرار التنفيذ ---
126
+ if sniper_result['signal'] == 'BUY':
127
+ print(f" 🔥 [L2 CONFIRMED] {symbol} (Conf: {sniper_result['confidence_prob']:.2f}). جاري تنفيذ الشراء...")
128
+ # (إضافة معلومات القناص إلى بيانات الإشارة)
129
+ signal_data['l2_sniper_result'] = sniper_result
130
 
131
+ async with self.execution_lock:
132
+ if symbol in self.open_positions: return # (تحقق مزدوج)
133
+ await self._execute_entry_from_signal(symbol, signal_data)
134
+ else:
135
+ # (هنا نضيفها إلى "قائمة المراقبة" إذا فشلت في L2)
136
+ print(f" -> [L2 REJECT] {symbol} (Conf: {sniper_result['confidence_prob']:.2f}). إضافة إلى قائمة المراقبة.")
137
+ await self._add_to_watchlist(symbol, signal_data)
 
 
138
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  except Exception as e:
140
+ print(f"❌ [TradeManager] فشل فادح أثناء معالجة إشارة L1/L2 لـ {symbol}: {e}")
141
  traceback.print_exc()
142
 
143
+ # ==============================================================================
144
+ # 🎯 [ 🚀 🚀 🚀 ]
145
+ # [ 💡 💡 💡 ] التعديل: تسجيل بيانات L2
146
+ # ==============================================================================
147
+ async def _execute_entry_from_signal(self, symbol, signal_data):
148
  """
149
+ تنفيذ عملية الدخول همي حالياً) بعد تأكيد L1 (و L2 إن وجد).
150
+ (هذه الدالة يجب أن تكون محمية بـ self.execution_lock قبل استدعائها)
151
  """
152
+ try:
153
+ trade_id = str(uuid.uuid4())
154
+ current_price = signal_data.get('current_price', 0.0)
155
+ if current_price == 0.0:
156
+ print(f"⚠️ [Entry ERROR] {symbol} السعر لا يمكن التنفيذ.")
157
+ return
158
+
159
+ # (استخراج بيانات L2 Sniper إذا كانت موجودة)
160
+ l2_result = signal_data.get('l2_sniper_result', {})
161
+ l2_confidence = l2_result.get('confidence_prob', 0.0)
162
+ l2_threshold = l2_result.get('threshold', 0.0)
 
 
 
 
 
163
 
164
+ # --- تحديد الأهداف (TP/SL) ---
165
+ # (هنا يجب أن نستخدم الأهداف الديناميكية من القناص أو الحارس)
166
+ # [TODO]: يجب تعديل 'sniper_engine' ليعيد 'atr' و 'tp_price'
167
+ # (حالياً، سنستخدم أهداف وهمية مؤقتة)
168
+ atr_mock = current_price * 0.02 # (2% ATR وهمي)
169
+ tp_price = current_price + (atr_mock * 2.0) # (R:R 2:1)
170
+ sl_price = current_price - (atr_mock * 1.0)
171
 
172
+ # (التحقق من صحة الأهداف)
173
+ if tp_price <= current_price or sl_price >= current_price:
174
+ print(f"⚠️ [Entry ERROR] {symbol} أهداف TP/SL غير صالحة. إلغاء.")
175
+ return
176
+
177
  new_trade = {
178
+ 'id': trade_id,
179
  'symbol': symbol,
180
+ 'entry_price': current_price,
181
+ 'entry_time': datetime.now().isoformat(),
182
  'status': 'OPEN',
183
+ 'tp_price': tp_price,
184
+ 'sl_price': sl_price,
185
+ 'last_update': datetime.now().isoformat(),
186
+
187
+ # [ 🚀 🚀 🚀 ]
188
+ # [ 💡 💡 💡 ] التعديل: إضافة بيانات L1 و L2
189
+ 'l1_score': signal_data.get('enhanced_final_score', 0.0),
190
+ 'l1_components': signal_data.get('components', {}),
191
+ 'l2_sniper_confidence': l2_confidence,
192
+ 'l2_sniper_threshold': l2_threshold
193
+ # [ 🚀 🚀 🚀 ]
194
  }
195
 
196
+ # --- (هنا يتم إرسال أمر الشراء الحقيقي للمنصة) ---
197
+ # (مثال:)
198
+ # order_success = await self.exchange_api.create_market_buy_order(symbol, ...)
199
+ # if not order_success:
200
+ # print(f"❌ [Entry ERROR] {symbol} فشل أمر المنصة.")
201
+ # return
202
+
203
+ # 1. تحديث الحالة الداخلية
204
  self.open_positions[symbol] = new_trade
205
+
206
+ # (إزالة من قائمة المراقبة إذا كانت موجودة)
207
  if symbol in self.watchlist:
208
  del self.watchlist[symbol]
 
 
 
 
 
209
 
210
+ # 2. حفظ في R2
211
  await self.r2.save_open_trades_async(list(self.open_positions.values()))
212
 
213
+ # 3. بدء حارس الخروج (Guardian Loop) لهذه الصفقة
214
+ if symbol in self.sentry_tasks:
215
+ self.sentry_tasks[symbol].cancel() # (إلغاء أي مهمة قديمة)
216
 
 
 
 
217
  self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
218
 
219
+ print(f"✅ [ENTRY EXECUTED] {symbol} | Price: {current_price:.4f} | TP: {tp_price:.4f} | SL: {sl_price:.4f}")
220
+ print(f" -> L1 Score: {new_trade['l1_score']:.2f}, L2 Conf: {new_trade['l2_sniper_confidence']:.2f}")
221
+
222
+ except Exception as e:
223
+ print(f"❌ [TradeManager] فشل فادح أثناء تنفيذ الدخول لـ {symbol}: {e}")
224
+ traceback.print_exc()
225
 
226
+ # ==============================================================================
227
+ # watchlist functions (من ملفك الأصلي)
228
+ # ==============================================================================
229
+ async def _add_to_watchlist(self, symbol, signal_data):
230
  """
231
+ إضافة عملة إلى قائمة المراقبة (Watchlist) لفحص L2 Sniper بشكل متكرر.
 
232
  """
233
+ # (منطق V15.1 الأصلي)
234
+ if symbol not in self.open_positions and symbol not in self.watchlist:
235
+ self.watchlist[symbol] = signal_data
236
+ print(f"👀 [Watchlist] {symbol} تمت إضافته للمراقبة (L1 Score: {signal_data['enhanced_final_score']:.2f})")
237
+
238
+ async def _find_signal_in_watchlist(self):
239
+ """
240
+ (V15.1) البحث في قائمة المراقبة عن إشارات L2 Sniper.
241
+ """
242
+ # (هذه الدالة تستدعي الآن القناص بشكل متكرر على العملات المرفوضة سابقاً)
243
+ if not self.sniper or not self.sniper.initialized:
244
+ return # (لا يمكن فعل شيء بدون القناص)
245
+
246
+ # (نعمل على نسخة لتجنب التعديل أثناء اللف)
247
+ watchlist_symbols = list(self.watchlist.keys())
248
+ if not watchlist_symbols:
249
+ return
250
+
251
+ print(f" -> [Watchlist] فحص {len(watchlist_symbols)} عملة في قائمة المراقبة لـ L2...")
252
 
253
+ for symbol in watchlist_symbols:
254
+ signal_data = self.watchlist.get(symbol)
255
+ if not signal_data: continue
256
+
257
+ # (التحقق من القناص مرة أخرى)
258
+ try:
259
+ ohlcv_1m = await self.data_manager.get_latest_ohlcv(symbol, '1m', 600)
260
+ if not ohlcv_1m or len(ohlcv_1m) < self.sniper.LOOKBACK_WINDOW:
261
+ continue # (لا تزال البيانات غير كافية)
262
+
263
+ sniper_result = await self.sniper.check_entry_signal_async(ohlcv_1m)
264
+
265
+ if sniper_result['signal'] == 'BUY':
266
+ print(f" 🔥 [L2 WATCHLIST HIT] {symbol} (Conf: {sniper_result['confidence_prob']:.2f}). جاري التنفيذ...")
267
+ signal_data['l2_sniper_result'] = sniper_result
268
+
269
+ async with self.execution_lock:
270
+ if symbol in self.open_positions: continue # (تحقق مزدوج)
271
+ # (إزالة من قائمة المراقبة قبل التنفيذ)
272
+ if symbol in self.watchlist:
273
+ del self.watchlist[symbol]
274
+ await self._execute_entry_from_signal(symbol, signal_data)
275
+
276
+ # (إذا لم تكن الإشارة جاهزة، تبقى في قائمة المراقبة)
277
+
278
+ except Exception as e:
279
+ print(f"❌ [Watchlist] فشل فحص L2 لـ {symbol}: {e}")
280
+ # (إزالة العملة إذا فشلت بشكل متكرر)
281
+ if symbol in self.watchlist:
282
+ del self.watchlist[symbol]
283
 
284
+
285
+ # ==============================================================================
286
+ # [ 🛡️ 🛡️ 🛡️ ]
287
+ # دالة حارس الخروج (Guard V1/V2)
288
+ # [ (هذه الدالة (والدوال التي تليها) تبقى كما هي 100% من ملفك الأصلي) ]
289
+ # ==============================================================================
290
+ async def _guardian_loop(self, symbol: str):
291
+ """
292
+ (V15.1) حلقة المراقبة اللحظية (الحارس) التي تعمل في الخلفية لكل صفقة مفتوحة.
293
+ تستخدم الآن 'GuardEngine' (V2) للتحقق من الخروج على إطار 5 دقائق.
294
+ """
295
+ print(f"🛡️ [Sentry Activated] بدء الحراسة لـ {symbol}.")
296
+
297
+ while self.running and symbol in self.open_positions:
298
+ try:
299
+ # (الانتظار لمدة دقيقة واحدة بين كل فحص)
300
+ await asyncio.sleep(60)
301
 
302
+ trade = self.open_positions.get(symbol)
303
+ if not trade:
304
+ break # (تم إغلاق الصفقة بواسطة حلقة أخرى)
305
+
306
+ # --- 1. جلب السعر الحالي (للتحقق من TP/SL) ---
307
  current_price = await self.data_manager.get_latest_price_async(symbol)
308
+ if current_price == 0.0:
309
+ print(f"⚠️ [Sentry {symbol}] فشل جلب السعر، إعادة المحاولة...")
310
+ continue # (فشل جلب السعر، حاول مرة أخرى)
311
 
312
+ # --- 2. التحقق من الأهداف (TP/SL) ---
313
+ if current_price >= trade['tp_price']:
314
+ print(f"✅ [Sentry EXIT] {symbol} (TP Hit) at {current_price}")
315
+ async with self.execution_lock:
316
+ await self._execute_exit(symbol, current_price, "TP_HIT")
317
+ break # (إنهاء حلقة الحراسة)
318
 
 
319
  if current_price <= trade['sl_price']:
320
+ print(f"🛑 [Sentry EXIT] {symbol} (SL Hit) at {current_price}")
321
+ async with self.execution_lock:
322
+ await self._execute_exit(symbol, current_price, "SL_HIT")
323
+ break # (إنهاء حلقة الحراسة)
324
+
325
+ # --- 3. [ 🚀 🚀 🚀 ]
326
+ # استدعاء حارس الخروج V2 (الدرع)
327
+ # --- [ 🚀 🚀 🚀 ]
328
+ if self.guard and self.guard.initialized:
329
+ # (جلب بيانات 5 دقائق كافية للحارس)
330
+ ohlcv_5m = await self.data_manager.get_latest_ohlcv(symbol, '5m', 250)
 
 
 
 
331
 
332
+ if len(ohlcv_5m) >= 250: # (الحد الأدنى الذي يحتاجه الحارس V2)
333
+ exit_check = await self.guard.check_exit_signal_async(ohlcv_5m)
 
334
 
335
+ if exit_check['action'] == 'EXIT_NOW':
336
+ print(f"🛡️ [Guard V2 EXIT] {symbol} (Conf: {exit_check['confidence']:.2f})")
337
+ async with self.execution_lock:
338
+ await self._execute_exit(symbol, current_price, "GUARD_V2_SIGNAL")
339
+ break # (إنهاء حلقة الحراسة)
340
+ else:
341
+ # print(f" -> [Guard V2 OK] {symbol} (Conf: {exit_check['confidence']:.2f})")
342
+ pass
343
+ else:
344
+ print(f"⚠️ [Guard V2 Skip] {symbol} بيانات 5m غير كافية للحارس.")
 
 
 
 
 
 
345
 
346
+ # (تحديث وقت المراقبة)
347
+ self.open_positions[symbol]['last_update'] = datetime.now().isoformat()
348
+
349
+ except asyncio.CancelledError:
350
+ print(f"🛡️ [Sentry STOP] تم إيقاف الحارس لـ {symbol}.")
351
+ break
352
+ except Exception as e:
353
+ print(f"❌ [Sentry ERROR] فشل فادح في حلقة الحراسة لـ {symbol}: {e}")
354
+ traceback.print_exc()
355
+ await asyncio.sleep(60) # (انتظار أطول عند حدوث خطأ فادح)
356
+
357
+ # (دالة _execute_exit من ملفك الكامل)
358
+ async def _execute_exit(self, symbol, price, reason):
359
  """
360
+ تنفيذ عملية الخروج (وهمي حالياً)
361
+ (هذه الدالة يجب أن تكون محمية بـ self.execution_lock قبل استدعائها)
362
  """
363
+ if symbol not in self.open_positions:
364
+ print(f"⚠️ [Exit ERROR] {symbol} غير موجودة في الصفقات المفتوحة (ربما أغلقت للتو).")
365
+ return
366
+
367
+ try:
368
+ trade = self.open_positions.pop(symbol) # (إزالة من الصفقات المفتوحة)
369
+
370
+ # (هنا يتم إرسال أمر البيع الحقيقي للمنصة)
371
+ # order_success = await self.exchange_api.create_market_sell_order(...)
372
+ # if not order_success:
373
+ # print(f"❌ [Exit ERROR] {symbol} فشل أمر المنصة. إعادة الصفقة...")
374
+ # self.open_positions[symbol] = trade # (إعادة الصفقة)
375
+ # return # (إيقاف الخروج)
376
 
377
+ # (تحديث بيانات الصفقة وإرسالها إلى R2 (الأرشيف))
378
+ trade['status'] = 'CLOSED'
379
+ trade['exit_price'] = price
380
+ trade['exit_time'] = datetime.now().isoformat()
381
+ trade['exit_reason'] = reason
382
 
383
+ profit = (price - trade['entry_price']) / trade['entry_price']
384
+ trade['profit_pct'] = profit * 100
 
 
 
 
 
 
 
 
385
 
386
+ await self.r2.archive_closed_trade_async(trade)
 
387
 
388
+ # (حذف من الحالة الداخلية في R2)
 
389
  await self.r2.save_open_trades_async(list(self.open_positions.values()))
390
+
391
+ print(f"✅ [EXIT EXECUTED] {symbol} | Reason: {reason} | PnL: {trade['profit_pct']:.2f}%")
392
 
393
+ # (إيقاف مهمة الحراسة لهذه الصفقة)
394
+ if symbol in self.sentry_tasks:
395
+ self.sentry_tasks[symbol].cancel()
396
+ del self.sentry_tasks[symbol]
 
 
 
 
 
 
397
 
398
+ except Exception as e:
399
+ print(f" [TradeManager] فشل فادح أثناء تنفيذ الخروج لـ {symbol}: {e}")
400
+ traceback.print_exc()
401
+ # (إعادة الصفقة إذا فشل الإغلاق لضمان عدم ضياعها)
402
+ if symbol not in self.open_positions:
403
+ self.open_positions[symbol] = trade
404
+
405
+ # (دالة update_trade_targets من ملفك الكامل)
406
+ async def update_trade_targets(self, symbol, new_tp=None, new_sl=None, reason="MANUAL"):
407
+ """
408
+ تحديث أهداف (TP/SL) لصفقة مفتوحة.
409
+ """
410
  if symbol in self.open_positions:
411
  trade = self.open_positions[symbol]
412
  old_tp = trade['tp_price']
 
419
  # حفظ فوري لضمان عدم ضياع الأهداف الجديدة في حال إعادة التشغيل
420
  await self.r2.save_open_trades_async(list(self.open_positions.values()))
421
 
422
+ print(f"🎯 [Targets Updated] {symbol} | TP: {old_tp:.4f}->{trade['tp_price']:.4f} | SL: {old_sl:.4f}->{trade['sl_price']:.4f} | Reason: {reason}\")
423
  else:
424
+ print(f"⚠️ [Target Update Failed] {symbol} is not currently open.\")
425
 
426
+ # (دالة start_sentry_loops من ملفك الكامل)
427
  async def start_sentry_loops(self):
428
  """بدء أو استئناف جميع مهام الحراسة"""
429
  for symbol in list(self.open_positions.keys()):
430
  # إذا لم يكن هناك حارس نشط، نعين واحداً
431
  if symbol not in self.sentry_tasks or self.sentry_tasks[symbol].done():
432
+ print(f"🛡️ [Sentry Restart] Activating guardian for existing trade: {symbol}\")
433
  self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
434
 
435
+ # (دالة stop_sentry_loops من ملفك الكامل)
436
  async def stop_sentry_loops(self):
437
  """إيقاف نظيف لجميع المهام الخلفية"""
438
  print("🛑 [TradeManager] Stopping all sentry loops...")
 
440
  for sym, task in self.sentry_tasks.items():
441
  task.cancel()
442
 
443
+ # انتظار انتهاء المهام (اختياري)
444
+ await asyncio.sleep(1)
445
+ print("🛑 [TradeManager] All sentries stopped.")