Riy777 commited on
Commit
4e90a93
·
1 Parent(s): de3b861

Update trade_manager.py

Browse files
Files changed (1) hide show
  1. trade_manager.py +140 -102
trade_manager.py CHANGED
@@ -1,7 +1,8 @@
1
- # trade_manager.py (V8.6 - Full Production Version with Async Lock)
2
  import asyncio
3
  import json
4
  import uuid
 
5
  from datetime import datetime
6
 
7
  class TradeManager:
@@ -10,87 +11,119 @@ class TradeManager:
10
  self.data_manager = data_manager
11
  self.titan = titan_engine
12
  self.pattern_engine = pattern_engine
13
- # قواميس تتبع الحالة في الذاكرة
 
14
  self.open_positions = {}
15
  self.watchlist = {}
16
  self.sentry_tasks = {}
17
  self.running = True
 
18
  # 🔒 قفل لضمان عدم تضارب عمليات الشراء/البيع المتزامنة
 
19
  self.execution_lock = asyncio.Lock()
20
 
21
- print("🛡️ [TradeManager V8.6] Sentry Module Initialized (Locked Execution Mode).")
22
 
23
  async def initialize_sentry_exchanges(self):
24
  """
25
  تحميل الصفقات المفتوحة من R2 عند بدء تشغيل النظام لاستكمال إدارتها.
 
 
 
 
 
 
 
 
 
26
  """
27
  try:
 
28
  loaded_trades = await self.r2.get_open_trades_async()
 
29
  if loaded_trades:
 
 
 
 
30
  for t in loaded_trades:
31
  symbol = t['symbol']
32
- self.open_positions[symbol] = t
33
- print(f"🛡️ [TradeManager] Successfully resumed watching {len(self.open_positions)} active positions.")
34
- else:
35
- print("🛡️ [TradeManager] No active positions found in R2 to resume.")
 
 
 
 
 
 
36
  except Exception as e:
37
- print(f" [TradeManager] Error initializing trades from R2: {e}")
 
38
 
39
  async def update_sentry_watchlist(self, approved_candidates):
40
  """
41
- تحديث قائمة المراقبة النشطة للحارس (Sniper) بناءً على ترشيحات الدماغ.
42
  """
43
- # تفريغ القائمة القديمة وبدء قائمة جديدة
44
- self.watchlist.clear()
 
 
 
 
 
 
 
45
  for cand in approved_candidates:
46
  symbol = cand['symbol']
47
- self.watchlist[symbol] = {
48
- 'data': cand,
49
- 'added_at': datetime.utcnow().isoformat(),
50
- 'monitoring_started': False
51
- }
52
- print(f"📋 [Sniper] Watchlist updated with {len(self.watchlist)} elite targets.")
 
 
53
 
54
  # بدء مهام المراقبة الجديدة فوراً
55
  for symbol in list(self.watchlist.keys()):
56
- # إذا لم تكن المهمة تعمل بالفعل
57
  if symbol not in self.sentry_tasks or self.sentry_tasks[symbol].done():
58
- print(f"🎯 [Sniper] Locking on target: {symbol}")
59
  self.sentry_tasks[symbol] = asyncio.create_task(self._sniper_loop(symbol))
60
 
61
  async def _sniper_loop(self, symbol):
62
  """
63
- حلقة المراقبة التكتيكية (القناص) لكل عملة في القائمة.
64
- تنتظر اللحظة المناسبة للدخول بناءً على تأكيد زخم لحظي (5m).
65
  """
66
  print(f"👁️ [Sniper] Monitoring {symbol} for tactical entry...")
67
  consecutive_signals = 0
68
- required_signals = 2 # عدد الإشارات المتتالية المطلوبة للتأكيد
69
 
70
  try:
71
  while self.running and symbol in self.watchlist and symbol not in self.open_positions:
72
- # 1. فحص مبدئي سريع: هل وصلنا للحد الأقصى للصفقات؟
73
- # (لتخفيف الضغط على الـ API إذا كانت المحفظة ممتلئة)
74
  if len(self.open_positions) >= 1:
75
- # print(f"💤 [Sniper] {symbol} on hold. Max positions (1) reached.")
76
- await asyncio.sleep(60) # انتظار أطول
77
  continue
78
 
79
- # 2. جلب أحدث بيانات لحظية (شموع 5 دقائق)
80
  ohlcv = await self.data_manager.get_latest_ohlcv(symbol, timeframe='5m', limit=30)
81
- if not ohlcv:
82
  await asyncio.sleep(30)
83
  continue
84
 
85
- # 3. تحليل فوري باستخدام Titan (نسخة سريعة)
86
  titan_res = self.titan.predict({'5m': ohlcv})
87
  current_score = titan_res.get('score', 0.0)
88
 
89
- # 4. تقييم شروط الدخول التكتيكي
90
  is_signal = False
91
  entry_reason = ""
92
 
93
- # الشرط: زخم قوي جداً على فريم 5 دقائق
94
  if current_score > 0.80:
95
  is_signal = True
96
  entry_reason = f"Titan Surge ({current_score:.2f})"
@@ -100,55 +133,59 @@ class TradeManager:
100
  print(f"🔥 [Sniper] {symbol} Signal detected! ({consecutive_signals}/{required_signals}) - {entry_reason}")
101
 
102
  if consecutive_signals >= required_signals:
103
- # محاولة تنفيذ الشراء (محمية بالقفل)
104
  current_price = ohlcv[-1][4] # آخر سعر إغلاق
 
105
  executed = await self.execute_buy_order(symbol, entry_reason, current_price)
106
 
107
  if executed:
108
- # إذا تم الشراء بنجاح، نخرج من حلقة القناص
 
109
  break
110
  else:
111
- # إذا فشل الشراء (مثلاً سبقتنا عملة أخرى)، نعيد تعيين الإشارات
112
  consecutive_signals = 0
113
  else:
114
- # تبريد الإشارات إذا انخفض الزخم
115
  if consecutive_signals > 0:
116
  # print(f"❄️ [Sniper] {symbol} signal cooled off.")
117
  consecutive_signals = 0
118
 
119
- await asyncio.sleep(30) # فاصل زمني بين الفحوصات
120
 
121
  except asyncio.CancelledError:
122
- pass # إيقاف طبيعي للمهمة
 
123
  except Exception as e:
124
- print(f"❌ [Sniper Error] Loop failed for {symbol}: {e}")
 
125
 
126
  async def execute_buy_order(self, symbol, reason, price):
127
  """
128
- تنفيذ أمر الشراء الفعلي.
129
- محمي بـ execution_lock لمنع أي تضارب في حالة وجود إشارات متزامنة.
130
  """
131
  async with self.execution_lock:
132
- # 🛡️ التحقق المزدوج الحرج داخل القفل
133
- # هل امتلأت المحفظة أثناء انتظارنا للقفل؟
 
 
134
  if len(self.open_positions) >= 1:
135
- print(f"⚠️ [Execution Blocked] Cannot buy {symbol}. Max positions (1) reached just now by another trade.")
136
  return False
137
 
138
- # 1. جلب أحدث حالة للمحفظة من R2
139
  portfolio = await self.r2.get_portfolio_state_async()
140
  capital = portfolio.get('current_capital_usd', 0.0)
141
 
142
- # التحقق من وجود رصيد كافٍ (حد أدنى 10 دولار مثلاً)
143
- if capital < 10.0:
144
  print(f"⚠️ [Execution Failed] Insufficient capital for {symbol}: ${capital:.2f}")
145
  return False
146
 
147
- # 2. حساب كمية الشراء (استثمار شبه كامل للرصيد المتاح)
148
- invest_amount = capital * 0.98 # ترك هامش 2% للرسوم أو فروقات الأسعار
149
  quantity = invest_amount / price
150
 
151
- # 3. تجهيز سجل الصفقة الجديد
152
  trade_id = str(uuid.uuid4())[:8]
153
  new_trade = {
154
  'trade_id': trade_id,
@@ -159,19 +196,17 @@ class TradeManager:
159
  'quantity': quantity,
160
  'invested_usd': invest_amount,
161
  'entry_reason': reason,
162
- # أهداف أولية (سيقوم الدماغ بتحديثها لاحقاً في دورة إعادة التقييم)
163
- 'tp_price': price * 1.05, # هدف ربح مبدئي +5%
164
- 'sl_price': price * 0.95, # وقف خسارة مبدئي -5%
165
- 'highest_price': price # لتتبع الوقف المتحرك لاحقاً
166
  }
167
 
168
- # 4. تحديث الحالة الداخلية
169
  self.open_positions[symbol] = new_trade
170
- # إزالة من قائمة المراقبة لأنها أصبحت صفقة مفتوحة
171
  if symbol in self.watchlist:
172
  del self.watchlist[symbol]
173
 
174
- # 5. تحديث المحفظة وحفظ جميع الملفات في R2 (عملية ذرية قدر الإمكان)
175
  portfolio['current_capital_usd'] -= invest_amount
176
  portfolio['invested_capital_usd'] += invest_amount
177
  portfolio['total_trades'] += 1
@@ -181,59 +216,65 @@ class TradeManager:
181
 
182
  print(f"🚀 [EXECUTED] BUY {symbol} @ {price:.4f} | Invested: ${invest_amount:.2f} | Reason: {reason}")
183
 
184
- # 6. تشغيل الحارس (Guardian) لحماية الصفقة الجديدة
 
 
185
  self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
 
186
  return True
187
 
188
  async def _guardian_loop(self, symbol):
189
  """
190
- حلقة الحماية (Guardian) للصفقات المفتوحة.
191
- تراقب الأسعار وتنفذ أوامر الخروج (TP/SL) تلقائياً.
192
  """
193
- print(f"🛡️ [Guardian] Protecting position: {symbol}")
194
  try:
195
  while self.running and symbol in self.open_positions:
196
  trade = self.open_positions[symbol]
197
 
198
  # جلب السعر الحالي
199
  current_price = await self.data_manager.get_latest_price_async(symbol)
200
- if not current_price:
201
- await asyncio.sleep(30) # في حال فشل جلب السعر ننتظر قليلاً
202
  continue
203
 
204
- # تحديث أعلى سعر وصل له (للـ Trailing SL مستقبلاً)
205
  if current_price > trade.get('highest_price', 0):
206
  trade['highest_price'] = current_price
207
- # لا نحفظ في R2 كل تحديث بسيط لتوفير الموارد، فقط في الذاكرة مؤقتاً
208
 
209
- # التحقق من شروط الخروج
210
  if current_price <= trade['sl_price']:
 
211
  await self.execute_sell_order(symbol, "Stop Loss Hit", current_price)
212
  break
 
213
  elif current_price >= trade['tp_price']:
 
214
  await self.execute_sell_order(symbol, "Take Profit Hit", current_price)
215
  break
216
 
217
- await asyncio.sleep(10) # مراقبة كثيفة كل 10 ثواني
218
 
219
  except asyncio.CancelledError:
220
- pass
221
  except Exception as e:
222
- print(f"❌ [Guardian Error] Loop failed for {symbol}: {e}")
 
223
 
224
  async def execute_sell_order(self, symbol, reason, price):
225
  """
226
- تنفيذ أمر البيع وإغلاق الصفقة.
227
- محمي أيضاً بالقفل لضمان سلامة بيانات المحفظة.
228
  """
229
  async with self.execution_lock:
 
230
  if symbol not in self.open_positions:
 
231
  return
232
 
233
- # استخراج بيانات الصفقة
234
  trade = self.open_positions.pop(symbol)
235
 
236
- # حسابات الربح والخسارة
237
  revenue = trade['quantity'] * price
238
  profit_usd = revenue - trade['invested_usd']
239
  profit_pct = (profit_usd / trade['invested_usd']) * 100
@@ -247,59 +288,56 @@ class TradeManager:
247
  if profit_usd > 0:
248
  portfolio['winning_trades'] += 1
249
 
250
- # حفظ التحديثات في R2
251
  await self.r2.save_portfolio_state_async(portfolio)
252
  await self.r2.save_open_trades_async(list(self.open_positions.values()))
253
 
254
  print(f"💰 [SOLD] {symbol} @ {price:.4f} | PnL: ${profit_usd:.2f} ({profit_pct:+.2f}%) | Reason: {reason}")
255
-
256
- # (اختياري) يمكن هنا إضافة كود لحفظ الصفقة في سجل تاريخي دائم (Trade History)
257
 
258
  async def execute_emergency_exit(self, symbol, reason):
259
- """
260
- دالة مساعدة للخروج الطارئ عند استلام أمر مباشر من الدماغ.
261
- """
262
  current_price = await self.data_manager.get_latest_price_async(symbol)
263
- if current_price:
264
- print(f"🚨 [Emergency Exit] Initiating immediate sell for {symbol}...")
265
  await self.execute_sell_order(symbol, f"EMERGENCY: {reason}", current_price)
266
  else:
267
- print(f"❌ [Emergency Exit Failed] Could not fetch price for {symbol}")
268
 
269
  async def update_trade_targets(self, symbol, new_tp, new_sl, reason):
270
- """
271
- تحديث أهداف جني الأرباح ووقف الخسارة لصفقة مفتوحة بناءً على إعادة تقييم الدماغ.
272
- """
273
  if symbol in self.open_positions:
274
  trade = self.open_positions[symbol]
 
 
 
 
 
275
 
276
- if new_tp is not None:
277
- trade['tp_price'] = float(new_tp)
278
- if new_sl is not None:
279
- trade['sl_price'] = float(new_sl)
280
-
281
  self.open_positions[symbol] = trade
282
- # نحفظ التحديث فوراً في R2 لضمان استمراريته
283
  await self.r2.save_open_trades_async(list(self.open_positions.values()))
284
- print(f"🎯 [Targets Updated] {symbol}: New TP={trade['tp_price']:.4f}, New SL={trade['sl_price']:.4f} | {reason}")
 
285
  else:
286
- print(f"⚠️ [Target Update Failed] {symbol} is not actively traded.")
287
 
288
  async def start_sentry_loops(self):
289
- """
290
- بدء تشغيل جميع مهام المراقبة (تستخدم عند بدء النظام).
291
- """
292
- # إعادة تفعيل الحراس لأي صفقات كانت مفتوحة
293
  for symbol in list(self.open_positions.keys()):
 
294
  if symbol not in self.sentry_tasks or self.sentry_tasks[symbol].done():
 
295
  self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
296
 
297
  async def stop_sentry_loops(self):
298
- """
299
- إيقاف جميع مهام المراقبة (عند إيقاف النظام).
300
- """
301
  self.running = False
302
- for symbol, task in self.sentry_tasks.items():
303
  task.cancel()
304
- await asyncio.gather(*self.sentry_tasks.values(), return_exceptions=True)
305
- print("🛡️ [TradeManager] All sentry loops stopped.")
 
 
 
 
 
1
+ # trade_manager.py (V8.7 - Full Production Version - NO ABBREVIATIONS)
2
  import asyncio
3
  import json
4
  import uuid
5
+ import traceback
6
  from datetime import datetime
7
 
8
  class TradeManager:
 
11
  self.data_manager = data_manager
12
  self.titan = titan_engine
13
  self.pattern_engine = pattern_engine
14
+
15
+ # قواميس تتبع الحالة في الذاكرة (In-Memory State)
16
  self.open_positions = {}
17
  self.watchlist = {}
18
  self.sentry_tasks = {}
19
  self.running = True
20
+
21
  # 🔒 قفل لضمان عدم تضارب عمليات الشراء/البيع المتزامنة
22
+ # هذا القفل يمنع حالتين من الدخول إلى منطقة التعديل على المحفظة في نفس الوقت
23
  self.execution_lock = asyncio.Lock()
24
 
25
+ print("🛡️ [TradeManager V8.7] Sentry Module Initialized (Sync & Lock Enabled).")
26
 
27
  async def initialize_sentry_exchanges(self):
28
  """
29
  تحميل الصفقات المفتوحة من R2 عند بدء تشغيل النظام لاستكمال إدارتها.
30
+ تعتمد الآن على وظيفة المزامنة المركزية.
31
+ """
32
+ print("🛡️ [TradeManager] Initializing and syncing state with R2...")
33
+ await self.sync_internal_state()
34
+
35
+ async def sync_internal_state(self):
36
+ """
37
+ مزامنة الحالة الداخلية مع R2 لضمان عدم فقدان أي صفقة مفتوحة.
38
+ تستخدم عند بدء النظام وعند بدء كل دورة تحليل جديدة لضمان تطابق البيانات.
39
  """
40
  try:
41
+ # جلب الصفقات المسجلة في R2
42
  loaded_trades = await self.r2.get_open_trades_async()
43
+
44
  if loaded_trades:
45
+ # إنشاء مجموعة من الرموز الموجودة في R2 للمقارنة
46
+ r2_symbols = set(t['symbol'] for t in loaded_trades)
47
+
48
+ # 1. إضافة الصفقات المفقودة من الذاكرة
49
  for t in loaded_trades:
50
  symbol = t['symbol']
51
+ if symbol not in self.open_positions:
52
+ self.open_positions[symbol] = t
53
+ print(f"🔄 [TradeManager] SYNC: Resumed watching trade from R2: {symbol}")
54
+
55
+ # 2. (اختياري) تنظيف الذاكرة من الصفقات التي أغلقت في R2 ولكنها علقت هنا
56
+ # يتم ذلك بمقارنة عكسية إذا تطلب الأمر مستقبلاً
57
+
58
+ # التأكد من أن جميع الصفقات المفتوحة لديها حراس يعملون
59
+ await self.start_sentry_loops()
60
+
61
  except Exception as e:
62
+ print(f"⚠️ [TradeManager] Critical State Sync Warning: {e}")
63
+ traceback.print_exc()
64
 
65
  async def update_sentry_watchlist(self, approved_candidates):
66
  """
67
+ تحديث قائمة المراقبة النشطة للحارس (Sniper) بناءً على ترشيحات الدماغ الجديدة.
68
  """
69
+ # إيقاف المهام القديمة التي لم تعد في القائمة الجديدة (تنظيف)
70
+ new_symbols = set(c['symbol'] for c in approved_candidates)
71
+ for symbol in list(self.watchlist.keys()):
72
+ if symbol not in new_symbols:
73
+ if symbol in self.sentry_tasks:
74
+ self.sentry_tasks[symbol].cancel()
75
+ del self.watchlist[symbol]
76
+
77
+ # إضافة المرشحين الجدد
78
  for cand in approved_candidates:
79
  symbol = cand['symbol']
80
+ if symbol not in self.watchlist:
81
+ self.watchlist[symbol] = {
82
+ 'data': cand,
83
+ 'added_at': datetime.utcnow().isoformat(),
84
+ 'monitoring_started': False
85
+ }
86
+
87
+ print(f"📋 [Sniper] Watchlist updated. Active targets: {len(self.watchlist)}")
88
 
89
  # بدء مهام المراقبة الجديدة فوراً
90
  for symbol in list(self.watchlist.keys()):
91
+ # نتأكد من عدم وجود مهمة قائمة بالفعل لنفس الرمز
92
  if symbol not in self.sentry_tasks or self.sentry_tasks[symbol].done():
93
+ print(f"🎯 [Sniper] Locking radar on target: {symbol}")
94
  self.sentry_tasks[symbol] = asyncio.create_task(self._sniper_loop(symbol))
95
 
96
  async def _sniper_loop(self, symbol):
97
  """
98
+ حلقة المراقبة التكتيكية (القناص) - تراقب الرمز لحظة بلحظة لاقتناص فرصة الدخول.
 
99
  """
100
  print(f"👁️ [Sniper] Monitoring {symbol} for tactical entry...")
101
  consecutive_signals = 0
102
+ required_signals = 2 # عدد الشموع المتتالية المطلوبة لتأكيد الزخم
103
 
104
  try:
105
  while self.running and symbol in self.watchlist and symbol not in self.open_positions:
106
+ # 1. فحص سريع: هل وصلنا للحد الأقصى للصفقات؟
107
+ # إذا نعم، نهدئ اللعب لتوفير الموارد
108
  if len(self.open_positions) >= 1:
109
+ await asyncio.sleep(60) # انتظار دقيقة كاملة قبل الفحص التالي
 
110
  continue
111
 
112
+ # 2. جلب بيانات الشموع اللحظية (5 دقائق)
113
  ohlcv = await self.data_manager.get_latest_ohlcv(symbol, timeframe='5m', limit=30)
114
+ if not ohlcv or len(ohlcv) < 20:
115
  await asyncio.sleep(30)
116
  continue
117
 
118
+ # 3. تحليل فوري باستخدام محرك تيتان
119
  titan_res = self.titan.predict({'5m': ohlcv})
120
  current_score = titan_res.get('score', 0.0)
121
 
122
+ # 4. تقييم الإشارة
123
  is_signal = False
124
  entry_reason = ""
125
 
126
+ # شرط الدخول: زخم قوي جداً (فوق 0.80)
127
  if current_score > 0.80:
128
  is_signal = True
129
  entry_reason = f"Titan Surge ({current_score:.2f})"
 
133
  print(f"🔥 [Sniper] {symbol} Signal detected! ({consecutive_signals}/{required_signals}) - {entry_reason}")
134
 
135
  if consecutive_signals >= required_signals:
136
+ # محاولة تنفيذ الشراء
137
  current_price = ohlcv[-1][4] # آخر سعر إغلاق
138
+ # نستخدم await هنا لأن الدالة execute_buy_order تحتوي على قفل async
139
  executed = await self.execute_buy_order(symbol, entry_reason, current_price)
140
 
141
  if executed:
142
+ # تم الشراء بنجاح، ننهي مهمة القناص هذه
143
+ print(f"✅ [Sniper] Mission accomplished for {symbol}. Standing down.")
144
  break
145
  else:
146
+ # فشل الشراء (ربما بسبب اكتمال العدد أو نقص الرصيد)، نعيد المحاولة لاحقاً
147
  consecutive_signals = 0
148
  else:
149
+ # إذا انقطعت سلسلة الإشارات، نعيد العداد للصفر
150
  if consecutive_signals > 0:
151
  # print(f"❄️ [Sniper] {symbol} signal cooled off.")
152
  consecutive_signals = 0
153
 
154
+ await asyncio.sleep(30) # فاصل زمني 30 ثانية بين كل فحص
155
 
156
  except asyncio.CancelledError:
157
+ # تم إلغاء المهمة يدوياً (مثلاً عند تحديث القائمة)
158
+ pass
159
  except Exception as e:
160
+ print(f"❌ [Sniper Error] Loop crashed for {symbol}: {e}")
161
+ traceback.print_exc()
162
 
163
  async def execute_buy_order(self, symbol, reason, price):
164
  """
165
+ تنفيذ أمر الشراء. هذه الدالة حرجة جداً ومحمية بقفل (Lock).
 
166
  """
167
  async with self.execution_lock:
168
+ # --- منطقة حرجة (Critical Section) ---
169
+ # لا يمكن لأي كود آخر تعديل الصفقات أو المحفظة أثناء وجودنا هنا
170
+
171
+ # 1. التحقق النهائي من عدد الصفقات
172
  if len(self.open_positions) >= 1:
173
+ print(f"⚠️ [Execution Blocked] Cannot buy {symbol}. Max positions (1) reached.")
174
  return False
175
 
176
+ # 2. جلب وتحديث المحفظة
177
  portfolio = await self.r2.get_portfolio_state_async()
178
  capital = portfolio.get('current_capital_usd', 0.0)
179
 
180
+ if capital < 5.0: # حد أدنى للرصيد (مثلاً 5 دولار)
 
181
  print(f"⚠️ [Execution Failed] Insufficient capital for {symbol}: ${capital:.2f}")
182
  return False
183
 
184
+ # 3. حساب كمية الاستثمار
185
+ invest_amount = capital * 0.99 # استثمار 99% من الرصيد المتاح
186
  quantity = invest_amount / price
187
 
188
+ # 4. إنشاء سجل الصفقة
189
  trade_id = str(uuid.uuid4())[:8]
190
  new_trade = {
191
  'trade_id': trade_id,
 
196
  'quantity': quantity,
197
  'invested_usd': invest_amount,
198
  'entry_reason': reason,
199
+ 'tp_price': price * 1.05, # هدف أولي +5%
200
+ 'sl_price': price * 0.95, # وقف أولي -5%
201
+ 'highest_price': price # لتتبع الوقف المتحرك
 
202
  }
203
 
204
+ # 5. تحديث الحالة الداخلية
205
  self.open_positions[symbol] = new_trade
 
206
  if symbol in self.watchlist:
207
  del self.watchlist[symbol]
208
 
209
+ # 6. تحديث وحفظ البيانات في R2 (دفعة واحدة لضمان التناسق)
210
  portfolio['current_capital_usd'] -= invest_amount
211
  portfolio['invested_capital_usd'] += invest_amount
212
  portfolio['total_trades'] += 1
 
216
 
217
  print(f"🚀 [EXECUTED] BUY {symbol} @ {price:.4f} | Invested: ${invest_amount:.2f} | Reason: {reason}")
218
 
219
+ # 7. تشغيل الحارس فوراً
220
+ if symbol in self.sentry_tasks and not self.sentry_tasks[symbol].done():
221
+ self.sentry_tasks[symbol].cancel() # إلغاء مهمة القناص القديمة إن وجدت
222
  self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
223
+
224
  return True
225
 
226
  async def _guardian_loop(self, symbol):
227
  """
228
+ حلقة الحماية (Guardian) - تراقب الصفقة المفتوحة وتدير الخروج.
 
229
  """
230
+ print(f"🛡️ [Guardian] Activated for position: {symbol}")
231
  try:
232
  while self.running and symbol in self.open_positions:
233
  trade = self.open_positions[symbol]
234
 
235
  # جلب السعر الحالي
236
  current_price = await self.data_manager.get_latest_price_async(symbol)
237
+ if not current_price or current_price <= 0:
238
+ await asyncio.sleep(30)
239
  continue
240
 
241
+ # تحديث أعلى سعر وصل له (مهم للوقف المتحرك المستقبلي)
242
  if current_price > trade.get('highest_price', 0):
243
  trade['highest_price'] = current_price
244
+ # ملاحظة: لا نحفظ في R2 هنا لتجنب كثرة الكتابة، نعتمد على الذاكرة
245
 
246
+ # التحقق من شروط الخروج (SL / TP)
247
  if current_price <= trade['sl_price']:
248
+ print(f"🛡️ [Guardian] STOP LOSS triggered for {symbol} @ {current_price:.4f} (SL: {trade['sl_price']:.4f})")
249
  await self.execute_sell_order(symbol, "Stop Loss Hit", current_price)
250
  break
251
+
252
  elif current_price >= trade['tp_price']:
253
+ print(f"🛡️ [Guardian] TAKE PROFIT triggered for {symbol} @ {current_price:.4f} (TP: {trade['tp_price']:.4f})")
254
  await self.execute_sell_order(symbol, "Take Profit Hit", current_price)
255
  break
256
 
257
+ await asyncio.sleep(10) # مراقبة نشطة كل 10 ثواني
258
 
259
  except asyncio.CancelledError:
260
+ print(f"🛡️ [Guardian] Task cancelled for {symbol}")
261
  except Exception as e:
262
+ print(f"❌ [Guardian Error] Loop crashed for {symbol}: {e}")
263
+ traceback.print_exc()
264
 
265
  async def execute_sell_order(self, symbol, reason, price):
266
  """
267
+ تنفيذ أمر البيع. محمي بـ execution_lock.
 
268
  """
269
  async with self.execution_lock:
270
+ # --- منطقة حرجة ---
271
  if symbol not in self.open_positions:
272
+ print(f"⚠️ [Sell Failed] {symbol} not found in open positions during execution.")
273
  return
274
 
 
275
  trade = self.open_positions.pop(symbol)
276
 
277
+ # حسابات الأرباح
278
  revenue = trade['quantity'] * price
279
  profit_usd = revenue - trade['invested_usd']
280
  profit_pct = (profit_usd / trade['invested_usd']) * 100
 
288
  if profit_usd > 0:
289
  portfolio['winning_trades'] += 1
290
 
291
+ # حفظ التحديثات النهائية في R2
292
  await self.r2.save_portfolio_state_async(portfolio)
293
  await self.r2.save_open_trades_async(list(self.open_positions.values()))
294
 
295
  print(f"💰 [SOLD] {symbol} @ {price:.4f} | PnL: ${profit_usd:.2f} ({profit_pct:+.2f}%) | Reason: {reason}")
 
 
296
 
297
  async def execute_emergency_exit(self, symbol, reason):
298
+ """واجهة للخروج الطارئ اليدوي أو من الدماغ"""
299
+ print(f"🚨 [Emergency] Requested exit for {symbol} due to: {reason}")
 
300
  current_price = await self.data_manager.get_latest_price_async(symbol)
301
+ if current_price and current_price > 0:
 
302
  await self.execute_sell_order(symbol, f"EMERGENCY: {reason}", current_price)
303
  else:
304
+ print(f"❌ [Emergency Failed] Could not fetch valid price for {symbol}")
305
 
306
  async def update_trade_targets(self, symbol, new_tp, new_sl, reason):
307
+ """تحديث أهداف الصفقة بناءً على أوامر الدماغ"""
 
 
308
  if symbol in self.open_positions:
309
  trade = self.open_positions[symbol]
310
+ old_tp = trade['tp_price']
311
+ old_sl = trade['sl_price']
312
+
313
+ if new_tp is not None: trade['tp_price'] = float(new_tp)
314
+ if new_sl is not None: trade['sl_price'] = float(new_sl)
315
 
 
 
 
 
 
316
  self.open_positions[symbol] = trade
317
+ # حفظ فوري لضمان عدم ضياع الأهداف الجديدة في حال إعادة التشغيل
318
  await self.r2.save_open_trades_async(list(self.open_positions.values()))
319
+
320
+ print(f"🎯 [Targets Updated] {symbol} | TP: {old_tp:.4f}->{trade['tp_price']:.4f} | SL: {old_sl:.4f}->{trade['sl_price']:.4f} | Reason: {reason}")
321
  else:
322
+ print(f"⚠️ [Target Update Failed] {symbol} is not currently open.")
323
 
324
  async def start_sentry_loops(self):
325
+ """بدء أو استئناف جميع مهام الحراسة"""
 
 
 
326
  for symbol in list(self.open_positions.keys()):
327
+ # إذا لم يكن هناك حارس نشط، نعين واحداً
328
  if symbol not in self.sentry_tasks or self.sentry_tasks[symbol].done():
329
+ print(f"🛡️ [Sentry Restart] Activating guardian for existing trade: {symbol}")
330
  self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
331
 
332
  async def stop_sentry_loops(self):
333
+ """إيقاف نظيف لجميع المهام الخلفية"""
334
+ print("🛑 [TradeManager] Stopping all sentry loops...")
 
335
  self.running = False
336
+ for sym, task in self.sentry_tasks.items():
337
  task.cancel()
338
+
339
+ # انتظار انتهاء المهام (اختياري لكن أفضل للأمان)
340
+ if self.sentry_tasks:
341
+ await asyncio.gather(*self.sentry_tasks.values(), return_exceptions=True)
342
+
343
+ print("✅ [TradeManager] All loops stopped.")