Riy777 commited on
Commit
ada80bb
·
1 Parent(s): 2c79baa

Update trade_manager.py

Browse files
Files changed (1) hide show
  1. trade_manager.py +118 -129
trade_manager.py CHANGED
@@ -1,4 +1,4 @@
1
- # trade_manager.py (Updated to Sentry/Executor architecture)
2
  import asyncio
3
  import json
4
  import time
@@ -8,20 +8,20 @@ from typing import Dict, Any, List
8
  from collections import deque
9
 
10
  # 🔴 --- START OF CHANGE --- 🔴
11
- # (استيراد ccxt.pro هو التغيير الأهم)
 
12
  try:
13
- import ccxt.pro as ccxtpro
14
  CCXT_PRO_AVAILABLE = True
15
  except ImportError:
16
- print("❌❌❌ خطأ فادح: مكتبة 'ccxt-pro' غير موجودة. ❌❌❌")
17
- print("يرجى تثبيتها فوراً: pip install ccxt-pro")
18
  CCXT_PRO_AVAILABLE = False
19
  # 🔴 --- END OF CHANGE --- 🔴
20
 
 
21
  from helpers import safe_float_conversion
22
- # (لم نعد بحاجة إلى _DynamicExitEngine القديم أو pandas_ta هنا)
23
 
24
- # 🔴 --- START OF CHANGE --- 🔴
25
  # (فئة مساعد مبسطة لتتبع البيانات اللحظية)
26
  class TacticalData:
27
  def __init__(self, symbol):
@@ -30,7 +30,7 @@ class TacticalData:
30
  self.trades = deque(maxlen=100)
31
  self.cvd = 0.0 # (Cumulative Volume Delta)
32
  self.large_trades = []
33
- self.one_min_rsi = 50.0 # (سيتم تحديثه)
34
  self.last_update = time.time()
35
  self.binance_trades = deque(maxlen=50) # (للبيانات التأكيدية)
36
  self.binance_cvd = 0.0
@@ -39,7 +39,6 @@ class TacticalData:
39
  self.trades.append(trade)
40
  self.last_update = time.time()
41
 
42
- # حساب CVD
43
  try:
44
  trade_amount = float(trade['amount'])
45
  if trade['side'] == 'buy':
@@ -47,14 +46,13 @@ class TacticalData:
47
  else:
48
  self.cvd -= trade_amount
49
 
50
- # رصد صفقات الحيتان (Taker)
51
  trade_cost_usd = float(trade['cost'])
52
  if trade_cost_usd > 20000: # (عتبة 20,000 دولار)
53
  self.large_trades.append(trade)
54
  if len(self.large_trades) > 20: self.large_trades.pop(0)
55
 
56
  except Exception:
57
- pass # (تجاهل أخطاء التحويل)
58
 
59
  def add_binance_trade(self, trade):
60
  self.binance_trades.append(trade)
@@ -80,7 +78,6 @@ class TacticalData:
80
  bids = self.order_book.get('bids', [])
81
  asks = self.order_book.get('asks', [])
82
 
83
- # (يمكن إضافة منطق رصد الجدران هنا)
84
  bids_depth = sum(price * amount for price, amount in bids[:10])
85
  asks_depth = sum(price * amount for price, amount in asks[:10])
86
 
@@ -89,23 +86,40 @@ class TacticalData:
89
  return {"bids_depth": 0, "asks_depth": 0, "top_wall": "Error"}
90
 
91
  def get_1m_rsi(self):
92
- # (في نظام حقيقي، سنحسب هذا من 1-min OHLCV)
93
- # (هنا سنحاكيه بشكل مبسط جداً من آخر 20 صفقة)
94
  try:
95
  if len(self.trades) < 20:
96
  return 50.0
97
 
98
- closes = np.array([trade['price'] for trade in self.trades])
99
- if len(closes) < 2: return 50.0
 
100
 
101
  deltas = np.diff(closes)
102
  seed = deltas[:14]
103
  gains = np.sum(seed[seed >= 0])
104
- losses = np.sum(-seed[seed < 0])
105
 
106
- if losses == 0: return 100.0
 
 
 
 
 
 
107
  rs = gains / losses
108
  rsi = 100.0 - (100.0 / (1.0 + rs))
 
 
 
 
 
 
 
 
 
 
 
109
  self.one_min_rsi = rsi
110
  return rsi
111
  except Exception:
@@ -119,49 +133,45 @@ class TacticalData:
119
  "rsi_1m_approx": self.get_1m_rsi(),
120
  "ob_analysis": self.analyze_order_book()
121
  }
122
- # 🔴 --- END OF CHANGE --- 🔴
123
 
124
 
125
  class TradeManager:
126
  def __init__(self, r2_service, learning_hub=None, data_manager=None, state_manager=None):
127
  if not CCXT_PRO_AVAILABLE:
128
- raise RuntimeError("مكتبة 'ccxt-pro' غير مثبتة. لا يمكن تشغيل TradeManager.")
129
 
130
  self.r2_service = r2_service
131
  self.learning_hub = learning_hub
132
- self.data_manager = data_manager # (لجلب بيانات Layer 1 مثل OHLCV للتحليل الاستراتيجي)
133
  self.state_manager = state_manager
134
 
135
  self.is_running = False
136
- self.sentry_watchlist = {} # (تتم إدارتها بواسطة app.py) {'SOL/USDT': {'strategy_hint': 'breakout_momentum', ...}}
137
- self.sentry_tasks = {} # (المهام قيد التشغيل) {'SOL/USDT': asyncio.Task}
138
  self.tactical_data_cache = {} # (تخزين بيانات TacticalData)
139
 
140
- # (سيتم تهيئة هذه في initialize_sentry_exchanges)
141
  self.kucoin_ws = None
142
- self.binance_ws = None # (للبيانات التأكيدية)
143
 
144
- # 🔴 --- START OF CHANGE --- 🔴
145
  async def initialize_sentry_exchanges(self):
146
  """تهيئة منصات ccxt.pro (WebSockets)"""
147
  try:
148
- print("🔄 [Sentry] تهيئة منصات WebSocket (ccxt.pro)...")
 
149
  self.kucoin_ws = ccxtpro.kucoin({'newUpdates': True})
150
  self.binance_ws = ccxtpro.binance({'newUpdates': True})
151
- # (اختبار الاتصال)
152
  await self.kucoin_ws.load_markets()
153
  await self.binance_ws.load_markets()
154
  print("✅ [Sentry] منصات WebSocket (KuCoin, Binance) جاهزة.")
155
  except Exception as e:
156
- print(f"❌ [Sentry] فشل تهيئة ccxt.pro: {e}")
157
- print(" -> تأكد من إضافة 'ccxt-pro' إلى requirements.txt")
158
  if self.kucoin_ws: await self.kucoin_ws.close()
159
  if self.binance_ws: await self.binance_ws.close()
160
  raise
161
 
162
  async def start_sentry_and_monitoring_loops(self):
163
  """
164
- (يستبدل start_trade_monitoring)
165
  الحلقة الرئيسية للحارس (Sentry) ومراقب الخروج (Exit Monitor).
166
  """
167
  self.is_running = True
@@ -169,31 +179,20 @@ class TradeManager:
169
 
170
  while self.is_running:
171
  try:
172
- # 1. جلب قائمة المراقبة (من المستكشف)
173
- # (sentry_watchlist يتم تحديثه بواسطة update_sentry_watchlist)
174
  watchlist_symbols = set(self.sentry_watchlist.keys())
175
-
176
- # 2. جلب الصفقات المفتوحة (لمراقبة الخروج)
177
  open_trades = await self.get_open_trades()
178
  open_trade_symbols = {t['symbol'] for t in open_trades}
179
-
180
- # 3. دمج القائمتين: كل ما نحتاج لمراقبته
181
  symbols_to_monitor = watchlist_symbols.union(open_trade_symbols)
182
 
183
- # 4. بدء المهام للرموز الجديدة
184
  for symbol in symbols_to_monitor:
185
  if symbol not in self.sentry_tasks:
186
  print(f" [Sentry] بدء المراقبة التكتيكية لـ {symbol}")
187
  strategy_hint = self.sentry_watchlist.get(symbol, {}).get('strategy_hint', 'generic')
188
-
189
- # (إنشاء مخزن بيانات تكتيكية)
190
  if symbol not in self.tactical_data_cache:
191
  self.tactical_data_cache[symbol] = TacticalData(symbol)
192
-
193
  task = asyncio.create_task(self._monitor_symbol_activity(symbol, strategy_hint))
194
  self.sentry_tasks[symbol] = task
195
 
196
- # 5. إيقاف المهام للرموز القديمة
197
  for symbol in list(self.sentry_tasks.keys()):
198
  if symbol not in symbols_to_monitor:
199
  print(f" [Sentry] إيقاف المراقبة التكتيكية لـ {symbol}")
@@ -202,7 +201,7 @@ class TradeManager:
202
  if symbol in self.tactical_data_cache:
203
  del self.tactical_data_cache[symbol]
204
 
205
- await asyncio.sleep(15) # (التحقق من التغييرات في القائمة كل 15 ثانية)
206
 
207
  except Exception as error:
208
  print(f"❌ [Sentry] خطأ في الحلقة الرئيسية: {error}")
@@ -229,7 +228,6 @@ class TradeManager:
229
  (يتم استدعاؤها من app.py)
230
  تحديث قائمة المراقبة التي يستخدمها الحارس (Sentry).
231
  """
232
- # (تحويل القائمة إلى قاموس لسهولة البحث)
233
  self.sentry_watchlist = {c['symbol']: c for c in candidates}
234
  print(f"ℹ️ [Sentry] تم تحديث Watchlist. عدد المرشحين: {len(self.sentry_watchlist)}")
235
 
@@ -242,8 +240,6 @@ class TradeManager:
242
  'monitored_symbols': list(self.sentry_tasks.keys())
243
  }
244
 
245
- # 🔴 --- (نهاية دوال الإدارة، بدء دوال المراقبة) --- 🔴
246
-
247
  async def _monitor_symbol_activity(self, symbol: str, strategy_hint: str):
248
  """
249
  (قلب الحارس)
@@ -252,16 +248,14 @@ class TradeManager:
252
  if symbol not in self.tactical_data_cache:
253
  self.tactical_data_cache[symbol] = TacticalData(symbol)
254
 
255
- # (تحويل الرمز لـ Binance)
256
- binance_symbol = symbol
257
- if symbol == 'BTC/USDT': binance_symbol = 'BTC/USDT' # (مثال، قد نحتاج لآلية أفضل)
258
 
259
  try:
260
  await asyncio.gather(
261
  self._watch_kucoin_trades(symbol),
262
  self._watch_kucoin_orderbook(symbol),
263
- self._watch_binance_trades(binance_symbol), # (بيانات تأكيدية)
264
- self._run_tactical_analysis_loop(symbol, strategy_hint) # (المحلل)
265
  )
266
  except asyncio.CancelledError:
267
  print(f"ℹ️ [Sentry] تم إيقاف المراقبة لـ {symbol}.")
@@ -296,17 +290,16 @@ class TradeManager:
296
 
297
  async def _watch_binance_trades(self, symbol):
298
  """حلقة مراقبة الصفقات (Binance - تأكيدية)"""
299
- if symbol.endswith('/USDT'):
300
- symbol = symbol.replace('/USDT', 'USDT')
301
-
302
  while self.is_running:
303
  try:
304
  trades = await self.binance_ws.watch_trades(symbol)
305
  if symbol in self.tactical_data_cache:
306
- for trade in trades:
307
- self.tactical_data_cache[symbol].add_binance_trade(trade)
 
 
 
308
  except Exception as e:
309
- # (نتجاهل الأخطاء هنا لأنها بيانات تأكيدية)
310
  await asyncio.sleep(30)
311
 
312
  async def _run_tactical_analysis_loop(self, symbol: str, strategy_hint: str):
@@ -318,44 +311,36 @@ class TradeManager:
318
  await asyncio.sleep(1) # (التحليل كل ثانية)
319
 
320
  try:
321
- # (تجنب التحليل إذا كانت هناك إعادة تحليل استراتيجي)
322
  if self.state_manager.trade_analysis_lock.locked():
323
  continue
324
 
325
- # 1. جلب الصفقة المفتوحة (إن وجدت)
326
  trade = await self.get_trade_by_symbol(symbol)
327
  tactical_data = self.tactical_data_cache.get(symbol)
328
  if not tactical_data:
329
  continue
330
 
331
- # 2. جلب أحدث البيانات التكتيكية
332
  snapshot = tactical_data.get_tactical_snapshot()
333
 
334
  if trade:
335
  # --- وضع مراقب الخروج (Exit Monitor) ---
336
- # (هنا نستخدم snapshot للتحقق من شروط الخروج)
337
  exit_reason = self._check_exit_trigger(trade, snapshot)
338
  if exit_reason:
339
  print(f"🛑 [Sentry] زناد خروج تكتيكي لـ {symbol}: {exit_reason}")
340
- # (جلب السعر الحالي من دفتر الطلبات لتجنب استدعاء API)
341
- current_price = tactical_data.order_book['bids'][0][0] if tactical_data.order_book else None
342
  if current_price:
343
- # (استدعاء دالة الإغلاق الفوري)
344
  await self.immediate_close_trade(symbol, current_price, f"Tactical Exit: {exit_reason}")
345
- # (بعد الإغلاق، ستتم إزالة الرمز من المراقبة في الحلقة الرئيسية)
346
 
347
  else:
348
  # --- وضع الحارس (Sentry Mode) ---
349
- # (التحقق مما إذا كان الرمز لا يزال في قائمة المراقبة)
350
  if symbol in self.sentry_watchlist:
351
  trigger = self._check_entry_trigger(symbol, strategy_hint, snapshot)
352
  if trigger:
353
  print(f"✅ [Sentry] زناد دخول تكتيكي لـ {symbol} (استراتيجية: {strategy_hint})")
354
- # (إزالة الرمز من قائمة المراقبة لمنع الدخول المتعدد)
355
- del self.sentry_watchlist[symbol]
356
 
357
- # (استدعاء المنفذ)
358
- await self._execute_smart_entry(symbol, strategy_hint, snapshot)
 
359
 
360
  except Exception as e:
361
  print(f"❌ [Sentry] خطأ في حلقة التحليل التكتيكي لـ {symbol}: {e}")
@@ -366,7 +351,6 @@ class TradeManager:
366
  (دماغ الزناد التكتيكي)
367
  يحدد ما إذا كان يجب الدخول الآن بناءً على الاستراتيجية.
368
  """
369
- # (هذا هو المكان الذي يتم فيه دمج المؤشرات اللحظية)
370
  rsi = data.get('rsi_1m_approx', 50)
371
  cvd_kucoin = data.get('cvd_kucoin', 0)
372
  cvd_binance = data.get('cvd_binance', 0)
@@ -375,18 +359,22 @@ class TradeManager:
375
  # (يمكننا قراءة "الدلتا التكتيكية" من LearningHub هنا)
376
 
377
  if strategy_hint == 'breakout_momentum':
378
- # (مثال: نريد زخم قوي على المنصتين + اختراق RSI)
379
  if (cvd_kucoin > 0 and cvd_binance > 0 and
380
  large_trades > 0 and rsi > 60):
381
  print(f" [Trigger] {symbol} Breakout: CVD K={cvd_kucoin:.0f}, B={cvd_binance:.0f}, RSI={rsi:.1f}")
382
  return True
383
 
384
  elif strategy_hint == 'mean_reversion':
385
- # (مثال: نريد هبوطاً حاداً مع انعكاس CVD)
386
  if (rsi < 30 and (cvd_kucoin > 0 or cvd_binance > 0)):
387
  print(f" [Trigger] {symbol} Reversion: RSI={rsi:.1f}, CVD K={cvd_kucoin:.0f}")
388
  return True
389
 
 
 
 
 
 
 
390
  return False
391
 
392
  def _check_exit_trigger(self, trade: Dict, data: Dict) -> str:
@@ -394,39 +382,49 @@ class TradeManager:
394
  rsi = data.get('rsi_1m_approx', 50)
395
  cvd_kucoin = data.get('cvd_kucoin', 0)
396
 
397
- # (مثال: خروج سريع إذا انعكس الزخم)
398
  if trade.get('strategy') == 'breakout_momentum':
399
  if rsi < 40 and cvd_kucoin < 0:
400
  return "Momentum reversal (RSI < 40 + CVD Negative)"
401
 
402
- # (مثال: خروج إذا وصل RSI إلى ذروة الشراء المفرط)
403
  if trade.get('strategy') == 'mean_reversion':
404
  if rsi > 75:
405
  return "Mean Reversion Target Hit (RSI > 75)"
 
 
 
 
 
 
 
406
 
407
- return None # (لا يوجد سبب للخروج التكتيكي)
 
408
 
409
- # 🔴 --- (نهاية الحارس، بدء المنفذ) --- 🔴
 
 
 
410
 
411
- async def _execute_smart_entry(self, symbol: str, strategy_hint: str, tactical_data: Dict):
 
 
412
  """
413
  (المنفذ - Layer 3)
414
  تنفيذ الصفقة بذكاء مع آلية انزلاق.
415
  """
416
  print(f"🚀 [Executor] بدء تنفيذ الدخول الذكي لـ {symbol}...")
417
 
418
- # (التحقق من عدم وجود قفل استراتيجي)
419
  if self.state_manager.trade_analysis_lock.locked():
420
  print(f"⚠️ [Executor] تم إلغاء الدخول لـ {symbol} بسبب قفل التحليل الاستراتيجي.")
421
  return
422
 
423
- # (الحصول على قفل R2 لضمان عدم وجود تداخل)
424
  if not self.r2_service.acquire_lock():
425
  print(f"⚠️ [Executor] فشل في الحصول على قفل R2 لـ {symbol}. تم الإلغاء.")
426
  return
427
 
428
  try:
429
- # (التحقق مرة أخرى من عدم فتح الصفقة أثناء اتخاذ القرار)
430
  if await self.get_trade_by_symbol(symbol):
431
  print(f"ℹ️ [Executor] الصفقة {symbol} مفتوحة بالفعل. تم الإلغاء.")
432
  return
@@ -437,57 +435,59 @@ class TradeManager:
437
  print(f"❌ [Executor] رأس مال غير كافٍ لـ {symbol}.")
438
  return
439
 
440
- # (جلب السعر الحالي من البيانات اللحظية)
441
- current_price = self.tactical_data_cache[symbol].order_book['asks'][0][0]
442
- if not current_price:
443
  print(f"❌ [Executor] لا يمكن الحصول على السعر الحالي لـ {symbol}.")
444
  return
445
 
446
- amount_to_buy = available_capital / current_price
447
 
448
- # (تحديد أهداف الخروج - يمكن تحسينها باستخدام LearningHub)
449
- # (هنا نستخدم قيم ثابتة كمثال)
450
- stop_loss_price = current_price * 0.98 # (وقف خسارة 2%)
451
- take_profit_price = current_price * 1.03 # (هدف ربح 3%)
452
- exit_profile = "ATR_TRAILING" # (افتراضي من LearningHub)
 
453
 
454
  # --- آلية الانزلاق (Slippage Mechanism) ---
455
  max_slippage_percent = 0.005 # (0.5%)
456
- target_price = current_price * (1 + max_slippage_percent)
457
 
458
- print(f" [Executor] {symbol}: وضع أمر محدد (Limit Buy) بسعر {current_price} (الحد الأقصى {target_price})")
459
 
460
- # (تنفيذ الأمر - استخدام KuCoin الرئيسي)
461
- order = await self.kucoin_ws.create_limit_buy_order(symbol, amount_to_buy, current_price)
462
 
463
- # (انتظار التنفيذ - "مطاردة" مبسطة)
464
  await asyncio.sleep(5) # (الانتظار 5 ثوانٍ)
465
 
466
  order_status = await self.kucoin_ws.fetch_order(order['id'], symbol)
467
 
468
  if order_status['status'] == 'closed':
469
- # --- نجح التنفيذ ---
470
  final_entry_price = order_status['average']
471
  print(f"✅ [Executor] تم التنفيذ! {symbol} بسعر {final_entry_price}")
472
 
473
- # (تسجيل الصفقة في R2 - استخدام الدالة الداخلية)
474
  await self._save_trade_to_r2(
475
  symbol=symbol,
476
  entry_price=final_entry_price,
477
  position_size_usd=available_capital,
478
  strategy=strategy_hint,
479
  exit_profile=exit_profile,
 
480
  stop_loss=stop_loss_price,
481
  take_profit=take_profit_price,
482
- tactical_context=tactical_data # (تخزين بيانات الزناد)
 
483
  )
484
 
485
  else:
486
  # --- فشل التنفيذ (الانزلاق) ---
487
  print(f"⚠️ [Executor] فشل تنفيذ الأمر المحدد لـ {symbol} في الوقت المناسب. إلغاء الأمر.")
488
  await self.kucoin_ws.cancel_order(order['id'], symbol)
489
- # (يمكن إضافة منطق "المطاردة" هنا - إعادة المحاولة بسعر أعلى)
490
- # (للبساطة، سنلغي هذه المحاولة)
 
 
 
491
 
492
  except Exception as e:
493
  print(f"❌ [Executor] فشل فادح أثناء التنفيذ لـ {symbol}: {e}")
@@ -503,45 +503,44 @@ class TradeManager:
503
  strategy = kwargs.get('strategy')
504
  exit_profile = kwargs.get('exit_profile')
505
 
506
- # (إنشاء كائن الصفقة)
507
- expected_target_time = (datetime.now() + timedelta(minutes=15)).isoformat() # (افتراضي قصير)
508
 
 
 
 
 
 
 
 
 
 
 
509
  new_trade = {
510
  "id": str(int(datetime.now().timestamp())),
511
  "symbol": symbol,
512
  "entry_price": kwargs.get('entry_price'),
513
  "entry_timestamp": datetime.now().isoformat(),
514
- "decision_data": {
515
- # (هنا نضع بيانات المنفذ والحارس)
516
- "reasoning": f"Tactical entry by Sentry based on {strategy}",
517
- "strategy": strategy,
518
- "exit_profile": exit_profile,
519
- "exit_parameters": {}, # (يمكن ملؤها)
520
- "tactical_context_at_decision": kwargs.get('tactical_context', {}),
521
- # (البيانات من المستكشف (Layer 1) يجب تمريرها أيضاً)
522
- "explorer_decision_context": self.sentry_watchlist.get(symbol, {}).get('llm_decision_context', {})
523
- },
524
  "status": "OPEN",
525
  "stop_loss": kwargs.get('stop_loss'),
526
  "take_profit": kwargs.get('take_profit'),
527
- "dynamic_stop_loss": kwargs.get('stop_loss'), # (البدء بوقف الخسارة الأولي)
528
  "trade_type": "LONG",
529
  "position_size_usd": kwargs.get('position_size_usd'),
530
  "expected_target_minutes": 15,
531
  "expected_target_time": expected_target_time,
532
- "is_monitored": True, # (سيتم مراقبتها بواسطة Sentry للخروج)
533
  "strategy": strategy,
534
- "monitoring_started": True # (لأن Sentry يراقبها بالفعل)
535
  }
536
 
537
- # (تحديث R2)
538
  trades = await self.r2_service.get_open_trades_async()
539
  trades.append(new_trade)
540
  await self.r2_service.save_open_trades_async(trades)
541
 
542
  portfolio_state = await self.r2_service.get_portfolio_state_async()
543
  portfolio_state["invested_capital_usd"] = kwargs.get('position_size_usd')
544
- portfolio_state["current_capital_usd"] = 0.0 # (نفترض استخدام كل الرصيد)
545
  portfolio_state["total_trades"] = portfolio_state.get("total_trades", 0) + 1
546
  await self.r2_service.save_portfolio_state_async(portfolio_state)
547
 
@@ -557,9 +556,6 @@ class TradeManager:
557
  print(f"❌ [R2] فشل حفظ الصفقة لـ {symbol}: {e}")
558
  traceback.print_exc()
559
 
560
-
561
- # 🔴 --- (الدوال المتبقية هي من trade_manager القديم، مع تعديلات طفيفة) --- 🔴
562
-
563
  async def close_trade(self, trade_to_close, close_price, reason="System Close"):
564
  """(لا تغيير جوهري) - لا يزال مسؤولاً عن حساب PnL وتحديث R2 وتشغيل LearningHub"""
565
  try:
@@ -585,11 +581,9 @@ class TradeManager:
585
  trade_to_close['pnl_usd'] = pnl
586
  trade_to_close['pnl_percent'] = pnl_percent
587
 
588
- # (الأرشيف وتحديث الملخص - لا تغيير)
589
  await self._archive_closed_trade(trade_to_close)
590
  await self._update_trade_summary(trade_to_close)
591
 
592
- # (تحديث المحفظة - لا تغيير)
593
  portfolio_state = await self.r2_service.get_portfolio_state_async()
594
  current_capital = portfolio_state.get("current_capital_usd", 0)
595
  new_capital = current_capital + position_size + pnl
@@ -602,12 +596,10 @@ class TradeManager:
602
  portfolio_state["total_loss_usd"] = portfolio_state.get("total_loss_usd", 0.0) + abs(pnl)
603
  await self.r2_service.save_portfolio_state_async(portfolio_state)
604
 
605
- # (إزالة الصفقة من open_trades.json - لا تغيير)
606
  open_trades = await self.r2_service.get_open_trades_async()
607
  trades_to_keep = [t for t in open_trades if t.get('id') != trade_to_close.get('id')]
608
  await self.r2_service.save_open_trades_async(trades_to_keep)
609
 
610
- # (إيقاف المراقبة - الحلقة الرئيسية ستلتقط هذا)
611
  if symbol in self.sentry_tasks:
612
  print(f"ℹ️ [Sentry] الصفقة {symbol} أغلقت، ستتوقف المراقبة.")
613
 
@@ -616,7 +608,7 @@ class TradeManager:
616
  "new_capital": new_capital, "strategy": strategy, "reason": reason
617
  })
618
 
619
- # (تشغيل التعلم - لا تغيير، هذا هو قلب النظام)
620
  if self.learning_hub and self.learning_hub.initialized:
621
  print(f"🧠 [LearningHub] تشغيل التعلم (Reflector+Stats) لـ {symbol}...")
622
  await self.learning_hub.analyze_trade_and_learn(trade_to_close, reason)
@@ -662,19 +654,16 @@ class TradeManager:
662
  if re_analysis_decision.get('action') == "UPDATE_TRADE":
663
  trade_to_update['stop_loss'] = re_analysis_decision['new_stop_loss']
664
  trade_to_update['take_profit'] = re_analysis_decision['new_take_profit']
665
- # (ملاحظة: dynamic_stop_loss يُدار بواسطة Sentry، لكننا نحدث القيمة الأولية)
666
  trade_to_update['dynamic_stop_loss'] = re_analysis_decision['new_stop_loss']
667
  trade_to_update['decision_data']['exit_profile'] = re_analysis_decision['new_exit_profile']
668
  trade_to_update['decision_data']['exit_parameters'] = re_analysis_decision['new_exit_parameters']
669
  print(f" 🔄 (Explorer) {symbol}: Exit profile updated to {re_analysis_decision['new_exit_profile']}")
670
 
671
- # (تحديثات أخرى)
672
  new_expected_minutes = re_analysis_decision.get('new_expected_minutes', 15)
673
  trade_to_update['expected_target_minutes'] = new_expected_minutes
674
  trade_to_update['expected_target_time'] = (datetime.now() + timedelta(minutes=new_expected_minutes)).isoformat()
675
  trade_to_update['decision_data']['reasoning'] = re_analysis_decision.get('reasoning')
676
 
677
- # (حفظ التغييرات في R2)
678
  open_trades = await self.r2_service.get_open_trades_async()
679
  for i, trade in enumerate(open_trades):
680
  if trade.get('id') == trade_to_update.get('id'):
@@ -733,4 +722,4 @@ class TradeManager:
733
  except Exception as e: print(f"❌ Failed to get trade by symbol {symbol}: {e}"); return None
734
 
735
 
736
- print(f"✅ Trade Manager loaded - V5 (Sentry/Executor with ccxt.pro: {CCXT_PRO_AVAILABLE})")
 
1
+ # trade_manager.py (Updated to Sentry/Executor architecture V5.1 - Fixed ccxt import)
2
  import asyncio
3
  import json
4
  import time
 
8
  from collections import deque
9
 
10
  # 🔴 --- START OF CHANGE --- 🔴
11
+ # (هذا هو التصحيح بناءً على خطأ البناء)
12
+ # (لقد استبدلنا 'ccxt.pro' بـ 'ccxt.async_support' لأن ccxt v4+ يدمجها)
13
  try:
14
+ import ccxt.async_support as ccxtpro
15
  CCXT_PRO_AVAILABLE = True
16
  except ImportError:
17
+ print("❌❌❌ خطأ فادح: فشل استيراد 'ccxt.async_support'. ❌❌❌")
18
+ print("يرجى التأكد من تثبيت 'ccxt' (الإصدار 4 أو أحدث) بنجاح.")
19
  CCXT_PRO_AVAILABLE = False
20
  # 🔴 --- END OF CHANGE --- 🔴
21
 
22
+ import numpy as np # (نحتاج numpy لتحليل RSI البسيط)
23
  from helpers import safe_float_conversion
 
24
 
 
25
  # (فئة مساعد مبسطة لتتبع البيانات اللحظية)
26
  class TacticalData:
27
  def __init__(self, symbol):
 
30
  self.trades = deque(maxlen=100)
31
  self.cvd = 0.0 # (Cumulative Volume Delta)
32
  self.large_trades = []
33
+ self.one_min_rsi = 50.0
34
  self.last_update = time.time()
35
  self.binance_trades = deque(maxlen=50) # (للبيانات التأكيدية)
36
  self.binance_cvd = 0.0
 
39
  self.trades.append(trade)
40
  self.last_update = time.time()
41
 
 
42
  try:
43
  trade_amount = float(trade['amount'])
44
  if trade['side'] == 'buy':
 
46
  else:
47
  self.cvd -= trade_amount
48
 
 
49
  trade_cost_usd = float(trade['cost'])
50
  if trade_cost_usd > 20000: # (عتبة 20,000 دولار)
51
  self.large_trades.append(trade)
52
  if len(self.large_trades) > 20: self.large_trades.pop(0)
53
 
54
  except Exception:
55
+ pass
56
 
57
  def add_binance_trade(self, trade):
58
  self.binance_trades.append(trade)
 
78
  bids = self.order_book.get('bids', [])
79
  asks = self.order_book.get('asks', [])
80
 
 
81
  bids_depth = sum(price * amount for price, amount in bids[:10])
82
  asks_depth = sum(price * amount for price, amount in asks[:10])
83
 
 
86
  return {"bids_depth": 0, "asks_depth": 0, "top_wall": "Error"}
87
 
88
  def get_1m_rsi(self):
89
+ """حساب RSI مبسط جداً من آخر 20 صفقة (كمؤشر للزخم اللحظي)"""
 
90
  try:
91
  if len(self.trades) < 20:
92
  return 50.0
93
 
94
+ # (نستخدم سعر آخر 20 صفقة كبديل لشموع الدقيقة الواحدة)
95
+ closes = np.array([trade['price'] for trade in self.trades if 'price' in trade])[-20:]
96
+ if len(closes) < 15: return 50.0
97
 
98
  deltas = np.diff(closes)
99
  seed = deltas[:14]
100
  gains = np.sum(seed[seed >= 0])
101
+ losses = np.sum(np.abs(seed[seed < 0]))
102
 
103
+ if losses == 0:
104
+ self.one_min_rsi = 100.0
105
+ return 100.0
106
+ if gains == 0:
107
+ self.one_min_rsi = 0.0
108
+ return 0.0
109
+
110
  rs = gains / losses
111
  rsi = 100.0 - (100.0 / (1.0 + rs))
112
+
113
+ # (تحديث سلس لباقي الصفقات)
114
+ for delta in deltas[14:]:
115
+ gain = max(delta, 0)
116
+ loss = max(-delta, 0)
117
+ gains = (gains * 13 + gain) / 14
118
+ losses = (losses * 13 + loss) / 14
119
+
120
+ if losses == 0: rsi = 100.0
121
+ else: rs = gains / losses; rsi = 100.0 - (100.0 / (1.0 + rs))
122
+
123
  self.one_min_rsi = rsi
124
  return rsi
125
  except Exception:
 
133
  "rsi_1m_approx": self.get_1m_rsi(),
134
  "ob_analysis": self.analyze_order_book()
135
  }
 
136
 
137
 
138
  class TradeManager:
139
  def __init__(self, r2_service, learning_hub=None, data_manager=None, state_manager=None):
140
  if not CCXT_PRO_AVAILABLE:
141
+ raise RuntimeError("مكتبة 'ccxt.async_support' (جزء من ccxt) غير متاحة. لا يمكن تشغيل TradeManager.")
142
 
143
  self.r2_service = r2_service
144
  self.learning_hub = learning_hub
145
+ self.data_manager = data_manager
146
  self.state_manager = state_manager
147
 
148
  self.is_running = False
149
+ self.sentry_watchlist = {} # (تتم إدارتها بواسطة app.py)
150
+ self.sentry_tasks = {} # (المهام قيد التشغيل)
151
  self.tactical_data_cache = {} # (تخزين بيانات TacticalData)
152
 
 
153
  self.kucoin_ws = None
154
+ self.binance_ws = None
155
 
 
156
  async def initialize_sentry_exchanges(self):
157
  """تهيئة منصات ccxt.pro (WebSockets)"""
158
  try:
159
+ print("🔄 [Sentry] تهيئة منصات WebSocket (ccxt.async_support)...")
160
+ # (استخدام ccxtpro الذي هو الآن 'ccxt.async_support')
161
  self.kucoin_ws = ccxtpro.kucoin({'newUpdates': True})
162
  self.binance_ws = ccxtpro.binance({'newUpdates': True})
163
+
164
  await self.kucoin_ws.load_markets()
165
  await self.binance_ws.load_markets()
166
  print("✅ [Sentry] منصات WebSocket (KuCoin, Binance) جاهزة.")
167
  except Exception as e:
168
+ print(f"❌ [Sentry] فشل تهيئة ccxt.async_support: {e}")
 
169
  if self.kucoin_ws: await self.kucoin_ws.close()
170
  if self.binance_ws: await self.binance_ws.close()
171
  raise
172
 
173
  async def start_sentry_and_monitoring_loops(self):
174
  """
 
175
  الحلقة الرئيسية للحارس (Sentry) ومراقب الخروج (Exit Monitor).
176
  """
177
  self.is_running = True
 
179
 
180
  while self.is_running:
181
  try:
 
 
182
  watchlist_symbols = set(self.sentry_watchlist.keys())
 
 
183
  open_trades = await self.get_open_trades()
184
  open_trade_symbols = {t['symbol'] for t in open_trades}
 
 
185
  symbols_to_monitor = watchlist_symbols.union(open_trade_symbols)
186
 
 
187
  for symbol in symbols_to_monitor:
188
  if symbol not in self.sentry_tasks:
189
  print(f" [Sentry] بدء المراقبة التكتيكية لـ {symbol}")
190
  strategy_hint = self.sentry_watchlist.get(symbol, {}).get('strategy_hint', 'generic')
 
 
191
  if symbol not in self.tactical_data_cache:
192
  self.tactical_data_cache[symbol] = TacticalData(symbol)
 
193
  task = asyncio.create_task(self._monitor_symbol_activity(symbol, strategy_hint))
194
  self.sentry_tasks[symbol] = task
195
 
 
196
  for symbol in list(self.sentry_tasks.keys()):
197
  if symbol not in symbols_to_monitor:
198
  print(f" [Sentry] إيقاف المراقبة التكتيكية لـ {symbol}")
 
201
  if symbol in self.tactical_data_cache:
202
  del self.tactical_data_cache[symbol]
203
 
204
+ await asyncio.sleep(15)
205
 
206
  except Exception as error:
207
  print(f"❌ [Sentry] خطأ في الحلقة الرئيسية: {error}")
 
228
  (يتم استدعاؤها من app.py)
229
  تحديث قائمة المراقبة التي يستخدمها الحارس (Sentry).
230
  """
 
231
  self.sentry_watchlist = {c['symbol']: c for c in candidates}
232
  print(f"ℹ️ [Sentry] تم تحديث Watchlist. عدد المرشحين: {len(self.sentry_watchlist)}")
233
 
 
240
  'monitored_symbols': list(self.sentry_tasks.keys())
241
  }
242
 
 
 
243
  async def _monitor_symbol_activity(self, symbol: str, strategy_hint: str):
244
  """
245
  (قلب الحارس)
 
248
  if symbol not in self.tactical_data_cache:
249
  self.tactical_data_cache[symbol] = TacticalData(symbol)
250
 
251
+ binance_symbol = symbol.replace('/USDT', 'USDT')
 
 
252
 
253
  try:
254
  await asyncio.gather(
255
  self._watch_kucoin_trades(symbol),
256
  self._watch_kucoin_orderbook(symbol),
257
+ self._watch_binance_trades(binance_symbol),
258
+ self._run_tactical_analysis_loop(symbol, strategy_hint)
259
  )
260
  except asyncio.CancelledError:
261
  print(f"ℹ️ [Sentry] تم إيقاف المراقبة لـ {symbol}.")
 
290
 
291
  async def _watch_binance_trades(self, symbol):
292
  """حلقة مراقبة الصفقات (Binance - تأكيدية)"""
 
 
 
293
  while self.is_running:
294
  try:
295
  trades = await self.binance_ws.watch_trades(symbol)
296
  if symbol in self.tactical_data_cache:
297
+ # (يجب تحويل الرمز مرة أخرى ليتطابق مع مفتاح القاموس)
298
+ cache_key = symbol.replace('USDT', '/USDT')
299
+ if cache_key in self.tactical_data_cache:
300
+ for trade in trades:
301
+ self.tactical_data_cache[cache_key].add_binance_trade(trade)
302
  except Exception as e:
 
303
  await asyncio.sleep(30)
304
 
305
  async def _run_tactical_analysis_loop(self, symbol: str, strategy_hint: str):
 
311
  await asyncio.sleep(1) # (التحليل كل ثانية)
312
 
313
  try:
 
314
  if self.state_manager.trade_analysis_lock.locked():
315
  continue
316
 
 
317
  trade = await self.get_trade_by_symbol(symbol)
318
  tactical_data = self.tactical_data_cache.get(symbol)
319
  if not tactical_data:
320
  continue
321
 
 
322
  snapshot = tactical_data.get_tactical_snapshot()
323
 
324
  if trade:
325
  # --- وضع مراقب الخروج (Exit Monitor) ---
 
326
  exit_reason = self._check_exit_trigger(trade, snapshot)
327
  if exit_reason:
328
  print(f"🛑 [Sentry] زناد خروج تكتيكي لـ {symbol}: {exit_reason}")
329
+ current_price = tactical_data.order_book['bids'][0][0] if tactical_data.order_book and tactical_data.order_book['bids'] else None
 
330
  if current_price:
 
331
  await self.immediate_close_trade(symbol, current_price, f"Tactical Exit: {exit_reason}")
 
332
 
333
  else:
334
  # --- وضع الحارس (Sentry Mode) ---
 
335
  if symbol in self.sentry_watchlist:
336
  trigger = self._check_entry_trigger(symbol, strategy_hint, snapshot)
337
  if trigger:
338
  print(f"✅ [Sentry] زناد دخول تكتيكي لـ {symbol} (استراتيجية: {strategy_hint})")
339
+ watchlist_entry = self.sentry_watchlist.pop(symbol) # (إزالة الرمز لمنع الدخول المتعدد)
 
340
 
341
+ # (تمرير سياق المستكشف (LLM) إلى المنفذ)
342
+ explorer_context = watchlist_entry.get('llm_decision_context', {})
343
+ await self._execute_smart_entry(symbol, strategy_hint, snapshot, explorer_context)
344
 
345
  except Exception as e:
346
  print(f"❌ [Sentry] خطأ في حلقة التحليل التكتيكي لـ {symbol}: {e}")
 
351
  (دماغ الزناد التكتيكي)
352
  يحدد ما إذا كان يجب الدخول الآن بناءً على الاستراتيجية.
353
  """
 
354
  rsi = data.get('rsi_1m_approx', 50)
355
  cvd_kucoin = data.get('cvd_kucoin', 0)
356
  cvd_binance = data.get('cvd_binance', 0)
 
359
  # (يمكننا قراءة "الدلتا التكتيكية" من LearningHub هنا)
360
 
361
  if strategy_hint == 'breakout_momentum':
 
362
  if (cvd_kucoin > 0 and cvd_binance > 0 and
363
  large_trades > 0 and rsi > 60):
364
  print(f" [Trigger] {symbol} Breakout: CVD K={cvd_kucoin:.0f}, B={cvd_binance:.0f}, RSI={rsi:.1f}")
365
  return True
366
 
367
  elif strategy_hint == 'mean_reversion':
 
368
  if (rsi < 30 and (cvd_kucoin > 0 or cvd_binance > 0)):
369
  print(f" [Trigger] {symbol} Reversion: RSI={rsi:.1f}, CVD K={cvd_kucoin:.0f}")
370
  return True
371
 
372
+ # (إضافة استراتيجيات أخرى)
373
+ elif strategy_hint == 'volume_spike':
374
+ if (large_trades > 2 and cvd_kucoin > 0):
375
+ print(f" [Trigger] {symbol} Volume Spike: LargeTrades={large_trades}, CVD K={cvd_kucoin:.0f}")
376
+ return True
377
+
378
  return False
379
 
380
  def _check_exit_trigger(self, trade: Dict, data: Dict) -> str:
 
382
  rsi = data.get('rsi_1m_approx', 50)
383
  cvd_kucoin = data.get('cvd_kucoin', 0)
384
 
385
+ # (مثال لخروج تكتيكي لاستراتيجية الاختراق)
386
  if trade.get('strategy') == 'breakout_momentum':
387
  if rsi < 40 and cvd_kucoin < 0:
388
  return "Momentum reversal (RSI < 40 + CVD Negative)"
389
 
390
+ # (مثال لخروج تكتيكي لاستراتيجية الانعكاس)
391
  if trade.get('strategy') == 'mean_reversion':
392
  if rsi > 75:
393
  return "Mean Reversion Target Hit (RSI > 75)"
394
+
395
+ # (التحقق من وقف الخسارة/الهدف الاستراتيجي)
396
+ # (نحتاج إلى سعر حالي دقيق)
397
+ if self.tactical_data_cache.get(trade['symbol']) and self.tactical_data_cache[trade['symbol']].order_book:
398
+ ob = self.tactical_data_cache[trade['symbol']].order_book
399
+ if ob['bids']:
400
+ current_price = ob['bids'][0][0] # (أفضل سعر شراء)
401
 
402
+ hard_stop = trade.get('stop_loss')
403
+ take_profit = trade.get('take_profit')
404
 
405
+ if hard_stop and current_price <= hard_stop:
406
+ return f"Strategic Stop Loss hit: {current_price} <= {hard_stop}"
407
+ if take_profit and current_price >= take_profit:
408
+ return f"Strategic Take Profit hit: {current_price} >= {take_profit}"
409
 
410
+ return None # (لا يوجد سبب للخروج التكتيكي)
411
+
412
+ async def _execute_smart_entry(self, symbol: str, strategy_hint: str, tactical_data: Dict, explorer_context: Dict):
413
  """
414
  (المنفذ - Layer 3)
415
  تنفيذ الصفقة بذكاء مع آلية انزلاق.
416
  """
417
  print(f"🚀 [Executor] بدء تنفيذ الدخول الذكي لـ {symbol}...")
418
 
 
419
  if self.state_manager.trade_analysis_lock.locked():
420
  print(f"⚠️ [Executor] تم إلغاء الدخول لـ {symbol} بسبب قفل التحليل الاستراتيجي.")
421
  return
422
 
 
423
  if not self.r2_service.acquire_lock():
424
  print(f"⚠️ [Executor] فشل في الحصول على قفل R2 لـ {symbol}. تم الإلغاء.")
425
  return
426
 
427
  try:
 
428
  if await self.get_trade_by_symbol(symbol):
429
  print(f"ℹ️ [Executor] الصفقة {symbol} مفتوحة بالفعل. تم الإلغاء.")
430
  return
 
435
  print(f"❌ [Executor] رأس مال غير كافٍ لـ {symbol}.")
436
  return
437
 
438
+ # (جلب السعر الحالي من البيانات اللحظية - سعر البيع)
439
+ current_ask_price = self.tactical_data_cache[symbol].order_book['asks'][0][0]
440
+ if not current_ask_price:
441
  print(f"❌ [Executor] لا يمكن الحصول على السعر الحالي لـ {symbol}.")
442
  return
443
 
444
+ amount_to_buy = available_capital / current_ask_price
445
 
446
+ # (تحديد أهداف الخروج بناءً على سياق المستكشف)
447
+ llm_decision = explorer_context.get('llm_decision_context', {}).get('decision', {})
448
+ stop_loss_price = llm_decision.get("stop_loss", current_ask_price * 0.98) # (افتراضي 2%)
449
+ take_profit_price = llm_decision.get("take_profit", current_ask_price * 1.03) # (افتراضي 3%)
450
+ exit_profile = llm_decision.get('exit_profile', 'ATR_TRAILING')
451
+ exit_parameters = llm_decision.get('exit_parameters', {})
452
 
453
  # --- آلية الانزلاق (Slippage Mechanism) ---
454
  max_slippage_percent = 0.005 # (0.5%)
455
+ max_buy_price = current_ask_price * (1 + max_slippage_percent)
456
 
457
+ print(f" [Executor] {symbol}: وضع أمر محدد (Limit Buy) بسعر {current_ask_price} (الحد الأقصى {max_buy_price})")
458
 
459
+ order = await self.kucoin_ws.create_limit_buy_order(symbol, amount_to_buy, current_ask_price)
 
460
 
 
461
  await asyncio.sleep(5) # (الانتظار 5 ثوانٍ)
462
 
463
  order_status = await self.kucoin_ws.fetch_order(order['id'], symbol)
464
 
465
  if order_status['status'] == 'closed':
 
466
  final_entry_price = order_status['average']
467
  print(f"✅ [Executor] تم التنفيذ! {symbol} بسعر {final_entry_price}")
468
 
 
469
  await self._save_trade_to_r2(
470
  symbol=symbol,
471
  entry_price=final_entry_price,
472
  position_size_usd=available_capital,
473
  strategy=strategy_hint,
474
  exit_profile=exit_profile,
475
+ exit_parameters=exit_parameters,
476
  stop_loss=stop_loss_price,
477
  take_profit=take_profit_price,
478
+ tactical_context=tactical_data,
479
+ explorer_context=explorer_context
480
  )
481
 
482
  else:
483
  # --- فشل التنفيذ (الانزلاق) ---
484
  print(f"⚠️ [Executor] فشل تنفيذ الأمر المحدد لـ {symbol} في الوقت المناسب. إلغاء الأمر.")
485
  await self.kucoin_ws.cancel_order(order['id'], symbol)
486
+ # (إعادة العملة إلى قائمة المراقبة بعد فترة هدوء)
487
+ await asyncio.sleep(60)
488
+ if symbol not in await self.get_open_trades():
489
+ self.sentry_watchlist[symbol] = {"symbol": symbol, "strategy_hint": strategy_hint, "llm_decision_context": explorer_context}
490
+
491
 
492
  except Exception as e:
493
  print(f"❌ [Executor] فشل فادح أثناء التنفيذ لـ {symbol}: {e}")
 
503
  strategy = kwargs.get('strategy')
504
  exit_profile = kwargs.get('exit_profile')
505
 
506
+ expected_target_time = (datetime.now() + timedelta(minutes=15)).isoformat()
 
507
 
508
+ # (دمج سياق المستكشف (Layer 1) مع سياق الحارس (Layer 2))
509
+ decision_data = {
510
+ "reasoning": f"Tactical entry by Sentry based on {strategy}",
511
+ "strategy": strategy,
512
+ "exit_profile": exit_profile,
513
+ "exit_parameters": kwargs.get('exit_parameters', {}),
514
+ "tactical_context_at_decision": kwargs.get('tactical_context', {}),
515
+ "explorer_decision_context": kwargs.get('explorer_context', {})
516
+ }
517
+
518
  new_trade = {
519
  "id": str(int(datetime.now().timestamp())),
520
  "symbol": symbol,
521
  "entry_price": kwargs.get('entry_price'),
522
  "entry_timestamp": datetime.now().isoformat(),
523
+ "decision_data": decision_data,
 
 
 
 
 
 
 
 
 
524
  "status": "OPEN",
525
  "stop_loss": kwargs.get('stop_loss'),
526
  "take_profit": kwargs.get('take_profit'),
527
+ "dynamic_stop_loss": kwargs.get('stop_loss'),
528
  "trade_type": "LONG",
529
  "position_size_usd": kwargs.get('position_size_usd'),
530
  "expected_target_minutes": 15,
531
  "expected_target_time": expected_target_time,
532
+ "is_monitored": True,
533
  "strategy": strategy,
534
+ "monitoring_started": True
535
  }
536
 
 
537
  trades = await self.r2_service.get_open_trades_async()
538
  trades.append(new_trade)
539
  await self.r2_service.save_open_trades_async(trades)
540
 
541
  portfolio_state = await self.r2_service.get_portfolio_state_async()
542
  portfolio_state["invested_capital_usd"] = kwargs.get('position_size_usd')
543
+ portfolio_state["current_capital_usd"] = 0.0
544
  portfolio_state["total_trades"] = portfolio_state.get("total_trades", 0) + 1
545
  await self.r2_service.save_portfolio_state_async(portfolio_state)
546
 
 
556
  print(f"❌ [R2] فشل حفظ الصفقة لـ {symbol}: {e}")
557
  traceback.print_exc()
558
 
 
 
 
559
  async def close_trade(self, trade_to_close, close_price, reason="System Close"):
560
  """(لا تغيير جوهري) - لا يزال مسؤولاً عن حساب PnL وتحديث R2 وتشغيل LearningHub"""
561
  try:
 
581
  trade_to_close['pnl_usd'] = pnl
582
  trade_to_close['pnl_percent'] = pnl_percent
583
 
 
584
  await self._archive_closed_trade(trade_to_close)
585
  await self._update_trade_summary(trade_to_close)
586
 
 
587
  portfolio_state = await self.r2_service.get_portfolio_state_async()
588
  current_capital = portfolio_state.get("current_capital_usd", 0)
589
  new_capital = current_capital + position_size + pnl
 
596
  portfolio_state["total_loss_usd"] = portfolio_state.get("total_loss_usd", 0.0) + abs(pnl)
597
  await self.r2_service.save_portfolio_state_async(portfolio_state)
598
 
 
599
  open_trades = await self.r2_service.get_open_trades_async()
600
  trades_to_keep = [t for t in open_trades if t.get('id') != trade_to_close.get('id')]
601
  await self.r2_service.save_open_trades_async(trades_to_keep)
602
 
 
603
  if symbol in self.sentry_tasks:
604
  print(f"ℹ️ [Sentry] الصفقة {symbol} أغلقت، ستتوقف المراقبة.")
605
 
 
608
  "new_capital": new_capital, "strategy": strategy, "reason": reason
609
  })
610
 
611
+ # (تشغيل التعلم - لا تغيير)
612
  if self.learning_hub and self.learning_hub.initialized:
613
  print(f"🧠 [LearningHub] تشغيل التعلم (Reflector+Stats) لـ {symbol}...")
614
  await self.learning_hub.analyze_trade_and_learn(trade_to_close, reason)
 
654
  if re_analysis_decision.get('action') == "UPDATE_TRADE":
655
  trade_to_update['stop_loss'] = re_analysis_decision['new_stop_loss']
656
  trade_to_update['take_profit'] = re_analysis_decision['new_take_profit']
 
657
  trade_to_update['dynamic_stop_loss'] = re_analysis_decision['new_stop_loss']
658
  trade_to_update['decision_data']['exit_profile'] = re_analysis_decision['new_exit_profile']
659
  trade_to_update['decision_data']['exit_parameters'] = re_analysis_decision['new_exit_parameters']
660
  print(f" 🔄 (Explorer) {symbol}: Exit profile updated to {re_analysis_decision['new_exit_profile']}")
661
 
 
662
  new_expected_minutes = re_analysis_decision.get('new_expected_minutes', 15)
663
  trade_to_update['expected_target_minutes'] = new_expected_minutes
664
  trade_to_update['expected_target_time'] = (datetime.now() + timedelta(minutes=new_expected_minutes)).isoformat()
665
  trade_to_update['decision_data']['reasoning'] = re_analysis_decision.get('reasoning')
666
 
 
667
  open_trades = await self.r2_service.get_open_trades_async()
668
  for i, trade in enumerate(open_trades):
669
  if trade.get('id') == trade_to_update.get('id'):
 
722
  except Exception as e: print(f"❌ Failed to get trade by symbol {symbol}: {e}"); return None
723
 
724
 
725
+ print(f"✅ Trade Manager loaded - V5.1 (Sentry/Executor with ccxt.async_support: {CCXT_PRO_AVAILABLE})")