Riy777 commited on
Commit
ac2081d
·
1 Parent(s): 5442b3b

Update trade_manager.py

Browse files
Files changed (1) hide show
  1. trade_manager.py +119 -36
trade_manager.py CHANGED
@@ -1,4 +1,4 @@
1
- # trade_manager.py (Updated to V6.9 - Active Tactical Profit-Saver)
2
  import asyncio
3
  import json
4
  import time
@@ -25,7 +25,17 @@ except ImportError:
25
  import numpy as np
26
  from helpers import safe_float_conversion
27
 
 
 
 
 
 
 
28
  class TacticalData:
 
 
 
 
29
  def __init__(self, symbol):
30
  self.symbol = symbol
31
  self.order_book = None
@@ -40,9 +50,17 @@ class TacticalData:
40
  self.last_kucoin_trade_id = None
41
  self.last_confirmation_trade_ids = defaultdict(lambda: None)
42
 
 
43
  self.ohlcv_1m = deque(maxlen=100)
44
  self.indicators_1m = {}
45
  self.last_1m_candle_timestamp = None
 
 
 
 
 
 
 
46
 
47
  def add_trade(self, trade):
48
  trade_id = trade.get('id')
@@ -91,6 +109,7 @@ class TacticalData:
91
  except Exception: return {"bids_depth": 0, "asks_depth": 0}
92
 
93
  def add_1m_ohlcv(self, ohlcv_data: List):
 
94
  if not ohlcv_data:
95
  return
96
 
@@ -109,6 +128,7 @@ class TacticalData:
109
  self._analyze_1m_indicators()
110
 
111
  def _analyze_1m_indicators(self):
 
112
  if ta is None or len(self.ohlcv_1m) < 26:
113
  self.indicators_1m = {}
114
  return
@@ -136,6 +156,24 @@ class TacticalData:
136
 
137
  except Exception as e:
138
  self.indicators_1m = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
  def get_tactical_snapshot(self):
141
  agg_cvd = sum(self.confirmation_cvd.values())
@@ -151,9 +189,7 @@ class TacticalData:
151
 
152
 
153
  class TradeManager:
154
- # 🔴 --- START OF CHANGE (V6.9) --- 🔴
155
  def __init__(self, r2_service, learning_hub=None, data_manager=None, state_manager=None, callback_on_close=None):
156
- # 🔴 --- END OF CHANGE --- 🔴
157
  if not CCXT_ASYNC_AVAILABLE:
158
  raise RuntimeError("مكتبة 'ccxt.async_support' غير متاحة.")
159
 
@@ -161,10 +197,7 @@ class TradeManager:
161
  self.learning_hub = learning_hub
162
  self.data_manager = data_manager
163
  self.state_manager = state_manager
164
-
165
- # 🔴 --- START OF CHANGE (V6.9) --- 🔴
166
  self.callback_on_close = callback_on_close
167
- # 🔴 --- END OF CHANGE --- 🔴
168
 
169
  self.is_running = False
170
  self.sentry_watchlist = {}
@@ -177,6 +210,12 @@ class TradeManager:
177
  self.confirmation_exchanges = {}
178
  self.polling_interval = 1.5
179
  self.confirmation_polling_interval = 3.0
 
 
 
 
 
 
180
 
181
  async def initialize_sentry_exchanges(self):
182
  try:
@@ -325,7 +364,7 @@ class TradeManager:
325
  del self.tactical_data_cache[symbol]
326
 
327
  async def _poll_kucoin_data(self, symbol):
328
- """(محدث V6.7) حلقة استقصاء (Polling) لبيانات KuCoin (تتضمن 1m OHLCV)"""
329
  while self.is_running:
330
  try:
331
  if not self.kucoin_rest:
@@ -333,11 +372,15 @@ class TradeManager:
333
  await asyncio.sleep(10)
334
  continue
335
 
 
 
336
  tasks = {
337
  'ob': asyncio.create_task(self.kucoin_rest.fetch_order_book(symbol, limit=20)),
338
  'trades': asyncio.create_task(self.kucoin_rest.fetch_trades(symbol, since=int((time.time() - 60) * 1000), limit=50)),
339
- 'ohlcv_1m': asyncio.create_task(self.kucoin_rest.fetch_ohlcv(symbol, '1m', limit=50))
 
340
  }
 
341
 
342
  await asyncio.wait(tasks.values(), return_when=asyncio.ALL_COMPLETED)
343
 
@@ -355,6 +398,11 @@ class TradeManager:
355
 
356
  if not tasks['ohlcv_1m'].exception():
357
  self.tactical_data_cache[symbol].add_1m_ohlcv(tasks['ohlcv_1m'].result())
 
 
 
 
 
358
 
359
  await asyncio.sleep(self.polling_interval)
360
 
@@ -411,7 +459,7 @@ class TradeManager:
411
 
412
 
413
  async def _run_tactical_analysis_loop(self, symbol: str, strategy_hint: str):
414
- """(محدث V6.9) (دماغ الحارس) يشغل التحليل التكتيكي كل ثانية."""
415
  while self.is_running:
416
  await asyncio.sleep(1)
417
  try:
@@ -423,13 +471,15 @@ class TradeManager:
423
  snapshot = tactical_data.get_tactical_snapshot()
424
 
425
  if trade:
426
- # 🔴 --- START OF CHANGE (V6.9) --- 🔴
427
  # 1. التحقق من SL/TP (الأولوية القصوى)
428
  exit_reason = self._check_exit_trigger(trade, snapshot, tactical_data)
429
 
430
- # 2. إذا لم يتم ضرب SL/TP، تحقق من "مراقب حماية الأرباح"
431
- if not exit_reason:
432
- exit_reason = self._check_tactical_profit_save(trade, snapshot, tactical_data)
 
 
 
433
  # 🔴 --- END OF CHANGE --- 🔴
434
 
435
  if exit_reason:
@@ -438,6 +488,7 @@ class TradeManager:
438
  current_price_to_close = None
439
  if "Take Profit" in exit_reason:
440
  current_price_to_close = trade.get('take_profit')
 
441
  elif tactical_data.order_book and tactical_data.order_book.get('bids') and len(tactical_data.order_book['bids']) > 0:
442
  current_price_to_close = tactical_data.order_book['bids'][0][0]
443
  else:
@@ -542,43 +593,77 @@ class TradeManager:
542
 
543
  return None
544
 
545
- # 🔴 --- START OF CHANGE (V6.9) --- 🔴
546
- def _check_tactical_profit_save(self, trade: Dict, data: Dict, tactical_data: TacticalData) -> str:
 
 
 
 
 
 
 
 
 
 
 
 
547
  """
548
- (جديد V6.9) "مراقب الانعكاس" 1-دقيقة.
549
- يغلق الصفقة الرابح�� فقط عند أول علامة ضعف تكتيكية.
550
  """
551
  try:
552
- # --- 1. التحقق من الربحية ---
553
-
554
- # (نستخدم سعر Bid، لأنه السعر الفعلي الذي سنبيع به)
555
  best_bid_price = None
556
  if tactical_data.order_book and tactical_data.order_book.get('bids') and len(tactical_data.order_book['bids']) > 0:
557
  best_bid_price = tactical_data.order_book['bids'][0][0]
558
 
559
  if best_bid_price is None:
560
- return None # لا يمكن تحديد السعر
561
 
562
  entry_price = trade.get('entry_price')
563
  if best_bid_price <= entry_price:
564
- return None # الصفقة ليست رابحة، لا تفعل شيئاً (حسب الطلب)
565
 
566
- # --- 2. الصفقة رابحة، التحقق من علامات الانهيار ---
567
- indicators_1m = data.get('indicators_1m', {})
568
- ema_9_1m = indicators_1m.get('ema_9')
569
- ema_21_1m = indicators_1m.get('ema_21')
570
-
571
- if ema_9_1m is None or ema_21_1m is None:
572
- return None # بيانات 1-دقيقة غير جاهزة بعد
573
 
574
- # (علامة الانهيار: تقاطع 1-دقيقة الهبوطي)
575
- if ema_9_1m < ema_21_1m:
576
- return f"Tactical Profit Save: 1m trend break (EMA9 < EMA21) while profitable."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
577
 
578
  return None
579
 
580
  except Exception as e:
581
- print(f"❌ [Sentry] خطأ في مراقب حماية الأرباح: {e}")
582
  return None
583
  # 🔴 --- END OF CHANGE --- 🔴
584
 
@@ -765,11 +850,9 @@ class TradeManager:
765
  await self.learning_hub.analyze_trade_and_learn(trade_to_close, reason)
766
  else: print("⚠️ [Sentry] LearningHub غير متاح، تم تخطي التعلم.")
767
 
768
- # 🔴 --- START OF CHANGE (V6.9) --- 🔴
769
  if self.callback_on_close:
770
  print("🔄 [Executor] Trade closed. Scheduling immediate Explorer cycle...")
771
  asyncio.create_task(self.callback_on_close())
772
- # 🔴 --- END OF CHANGE --- 🔴
773
 
774
  print(f"✅ [Executor] تم إغلاق الصفقة (الوهمية) {symbol} - السبب: {reason} - PnL: {pnl_percent:+.2f}%")
775
  return True
@@ -847,4 +930,4 @@ class TradeManager:
847
  except Exception as e: print(f"❌ Failed to get trade by symbol {symbol}: {e}"); return None
848
 
849
 
850
- print(f"✅ Trade Manager loaded - V6.9 (Active 1m Profit-Saver + Post-Close Cycle) (ccxt.async_support: {CCXT_ASYNC_AVAILABLE})")
 
1
+ # trade_manager.py (Updated to V7.0 - 5m Detector Profit-Saver)
2
  import asyncio
3
  import json
4
  import time
 
25
  import numpy as np
26
  from helpers import safe_float_conversion
27
 
28
+ # 🔴 --- START OF CHANGE (V7.0) --- 🔴
29
+ # (استيراد المحللات لتشغيل الكاشف المصغر 5m)
30
+ from ml_engine.indicators import AdvancedTechnicalAnalyzer
31
+ from ml_engine.patterns import ChartPatternAnalyzer
32
+ # 🔴 --- END OF CHANGE --- 🔴
33
+
34
  class TacticalData:
35
+ """
36
+ (محدث V7.0)
37
+ لتخزين بيانات 1m (للدخول) و 5m (لحماية الأرباح).
38
+ """
39
  def __init__(self, symbol):
40
  self.symbol = symbol
41
  self.order_book = None
 
50
  self.last_kucoin_trade_id = None
51
  self.last_confirmation_trade_ids = defaultdict(lambda: None)
52
 
53
+ # (بيانات 1-دقيقة لزناد الدخول)
54
  self.ohlcv_1m = deque(maxlen=100)
55
  self.indicators_1m = {}
56
  self.last_1m_candle_timestamp = None
57
+
58
+ # 🔴 --- START OF CHANGE (V7.0) --- 🔴
59
+ # (بيانات 5-دقائق لحماية الأرباح)
60
+ self.ohlcv_5m = deque(maxlen=100) # (لتخزين 100 شمعة 5-دقائق)
61
+ self.last_5m_candle_timestamp = None
62
+ self.new_5m_data_added = False # (مؤشر لتشغيل الكاشف 5m)
63
+ # 🔴 --- END OF CHANGE --- 🔴
64
 
65
  def add_trade(self, trade):
66
  trade_id = trade.get('id')
 
109
  except Exception: return {"bids_depth": 0, "asks_depth": 0}
110
 
111
  def add_1m_ohlcv(self, ohlcv_data: List):
112
+ """إضافة شموع 1-دقيقة وحساب المؤشرات (للدخول)"""
113
  if not ohlcv_data:
114
  return
115
 
 
128
  self._analyze_1m_indicators()
129
 
130
  def _analyze_1m_indicators(self):
131
+ """حساب مؤشرات 1-دقيقة الحقيقية (للدخول)"""
132
  if ta is None or len(self.ohlcv_1m) < 26:
133
  self.indicators_1m = {}
134
  return
 
156
 
157
  except Exception as e:
158
  self.indicators_1m = {}
159
+
160
+ # 🔴 --- START OF CHANGE (V7.0) --- 🔴
161
+ def add_5m_ohlcv(self, ohlcv_data: List):
162
+ """(جديد V7.0) إضافة شموع 5-دقائق (لحماية الأرباح)"""
163
+ if not ohlcv_data:
164
+ return
165
+
166
+ for candle in ohlcv_data:
167
+ timestamp = candle[0]
168
+ if timestamp and timestamp != self.last_5m_candle_timestamp:
169
+ if self.ohlcv_5m and timestamp < self.ohlcv_5m[-1][0]:
170
+ continue
171
+
172
+ self.ohlcv_5m.append(candle)
173
+ self.last_5m_candle_timestamp = timestamp
174
+ # (تعيين المؤشر لإعلام حلقة التحليل بوجود بيانات جديدة)
175
+ self.new_5m_data_added = True
176
+ # 🔴 --- END OF CHANGE --- 🔴
177
 
178
  def get_tactical_snapshot(self):
179
  agg_cvd = sum(self.confirmation_cvd.values())
 
189
 
190
 
191
  class TradeManager:
 
192
  def __init__(self, r2_service, learning_hub=None, data_manager=None, state_manager=None, callback_on_close=None):
 
193
  if not CCXT_ASYNC_AVAILABLE:
194
  raise RuntimeError("مكتبة 'ccxt.async_support' غير متاحة.")
195
 
 
197
  self.learning_hub = learning_hub
198
  self.data_manager = data_manager
199
  self.state_manager = state_manager
 
 
200
  self.callback_on_close = callback_on_close
 
201
 
202
  self.is_running = False
203
  self.sentry_watchlist = {}
 
210
  self.confirmation_exchanges = {}
211
  self.polling_interval = 1.5
212
  self.confirmation_polling_interval = 3.0
213
+
214
+ # 🔴 --- START OF CHANGE (V7.0) --- 🔴
215
+ # (تهيئة محللات الكاشف المصغر 5m)
216
+ self.sentry_technical_analyzer = AdvancedTechnicalAnalyzer()
217
+ self.sentry_pattern_analyzer = ChartPatternAnalyzer()
218
+ # 🔴 --- END OF CHANGE --- 🔴
219
 
220
  async def initialize_sentry_exchanges(self):
221
  try:
 
364
  del self.tactical_data_cache[symbol]
365
 
366
  async def _poll_kucoin_data(self, symbol):
367
+ """(محدث V7.0) حلقة استقصاء (Polling) لبيانات KuCoin (تتضمن 1m و 5m OHLCV)"""
368
  while self.is_running:
369
  try:
370
  if not self.kucoin_rest:
 
372
  await asyncio.sleep(10)
373
  continue
374
 
375
+ # 🔴 --- START OF CHANGE (V7.0) --- 🔴
376
+ # (جلب 4 أنواع بيانات بالتوازي)
377
  tasks = {
378
  'ob': asyncio.create_task(self.kucoin_rest.fetch_order_book(symbol, limit=20)),
379
  'trades': asyncio.create_task(self.kucoin_rest.fetch_trades(symbol, since=int((time.time() - 60) * 1000), limit=50)),
380
+ 'ohlcv_1m': asyncio.create_task(self.kucoin_rest.fetch_ohlcv(symbol, '1m', limit=50)),
381
+ 'ohlcv_5m': asyncio.create_task(self.kucoin_rest.fetch_ohlcv(symbol, '5m', limit=50))
382
  }
383
+ # 🔴 --- END OF CHANGE --- 🔴
384
 
385
  await asyncio.wait(tasks.values(), return_when=asyncio.ALL_COMPLETED)
386
 
 
398
 
399
  if not tasks['ohlcv_1m'].exception():
400
  self.tactical_data_cache[symbol].add_1m_ohlcv(tasks['ohlcv_1m'].result())
401
+
402
+ # 🔴 --- START OF CHANGE (V7.0) --- 🔴
403
+ if not tasks['ohlcv_5m'].exception():
404
+ self.tactical_data_cache[symbol].add_5m_ohlcv(tasks['ohlcv_5m'].result())
405
+ # 🔴 --- END OF CHANGE --- 🔴
406
 
407
  await asyncio.sleep(self.polling_interval)
408
 
 
459
 
460
 
461
  async def _run_tactical_analysis_loop(self, symbol: str, strategy_hint: str):
462
+ """(محدث V7.0) (دماغ الحارس) يشغل التحليل التكتيكي كل ثانية."""
463
  while self.is_running:
464
  await asyncio.sleep(1)
465
  try:
 
471
  snapshot = tactical_data.get_tactical_snapshot()
472
 
473
  if trade:
 
474
  # 1. التحقق من SL/TP (الأولوية القصوى)
475
  exit_reason = self._check_exit_trigger(trade, snapshot, tactical_data)
476
 
477
+ # 🔴 --- START OF CHANGE (V7.0) --- 🔴
478
+ # 2. إذا لم يتم ضرب SL/TP، تحقق من "مراقب حماية الأرباح" 5m
479
+ # (يتم تشغيله فقط إذا وصلت شمعة 5m جديدة)
480
+ if not exit_reason and tactical_data.new_5m_data_added:
481
+ exit_reason = await self._run_5m_profit_saver(trade, list(tactical_data.ohlcv_5m), tactical_data)
482
+ tactical_data.new_5m_data_added = False # (إعادة ضبط المؤشر)
483
  # 🔴 --- END OF CHANGE --- 🔴
484
 
485
  if exit_reason:
 
488
  current_price_to_close = None
489
  if "Take Profit" in exit_reason:
490
  current_price_to_close = trade.get('take_profit')
491
+ # (إذا كان الإغلاق تكتيكياً أو وقف خسارة، استخدم سعر Bid)
492
  elif tactical_data.order_book and tactical_data.order_book.get('bids') and len(tactical_data.order_book['bids']) > 0:
493
  current_price_to_close = tactical_data.order_book['bids'][0][0]
494
  else:
 
593
 
594
  return None
595
 
596
+ # 🔴 --- START OF CHANGE (V7.0) --- 🔴
597
+ def _create_dataframe_5m(self, candles: List) -> pd.DataFrame:
598
+ """(جديد V7.0) دالة مساعدة لإنشاء DataFrame لتحليل 5m"""
599
+ try:
600
+ if not candles: return pd.DataFrame()
601
+ df = pd.DataFrame(candles, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
602
+ df[['open', 'high', 'low', 'close', 'volume']] = df[['open', 'high', 'low', 'close', 'volume']].astype(float)
603
+ df.set_index('timestamp', inplace=True)
604
+ df.sort_index(inplace=True)
605
+ return df
606
+ except Exception:
607
+ return pd.DataFrame()
608
+
609
+ async def _run_5m_profit_saver(self, trade: Dict, ohlcv_5m_list: List, tactical_data: TacticalData) -> str:
610
  """
611
+ (جديد V7.0) "مراقب الانعكاس" 5m.
612
+ يستخدم الكاشف (مؤشرات + أنماط) على 5m لإغلاق الصفقات الرابحة.
613
  """
614
  try:
615
+ # --- 1. التحقق من الربحية (كما طلبت) ---
 
 
616
  best_bid_price = None
617
  if tactical_data.order_book and tactical_data.order_book.get('bids') and len(tactical_data.order_book['bids']) > 0:
618
  best_bid_price = tactical_data.order_book['bids'][0][0]
619
 
620
  if best_bid_price is None:
621
+ return None
622
 
623
  entry_price = trade.get('entry_price')
624
  if best_bid_price <= entry_price:
625
+ return None # الصفقة ليست رابحة، لا تفعل شيئاً
626
 
627
+ # --- 2. الصفقة رابحة، تشغيل الكاشف 5m ---
628
+ if len(ohlcv_5m_list) < 20:
629
+ return None # بيانات غير كافية للتحليل
 
 
 
 
630
 
631
+ df_5m = self._create_dataframe_5m(ohlcv_5m_list)
632
+ if df_5m.empty:
633
+ return None
634
+
635
+ # (تشغيل المحللات - بدون مونت كارلو كما طلبت)
636
+ indicators_5m = self.sentry_technical_analyzer.calculate_all_indicators(df_5m, '5m')
637
+ pattern_analysis_5m = await self.sentry_pattern_analyzer.detect_chart_patterns({'5m': ohlcv_5m_list})
638
+
639
+ # --- 3. حساب درجة الانعكاس (Reversal Score) ---
640
+ reversal_score = 0.0
641
+
642
+ # (التحقق من الأنماط الهبوطية)
643
+ pattern_name = pattern_analysis_5m.get('pattern_detected', '')
644
+ pattern_conf = pattern_analysis_5m.get('pattern_confidence', 0)
645
+ if pattern_name in ['Double Top', 'Downtrend', 'Breakout Down', 'Near Resistance'] and pattern_conf > 0.6:
646
+ reversal_score += 0.5
647
+
648
+ # (التحقق من المؤشرات السلبية)
649
+ rsi_5m = indicators_5m.get('rsi', 50)
650
+ macd_hist_5m = indicators_5m.get('macd_hist', 0)
651
+
652
+ # (إشارة ضعف 1: RSI تحت 50 + MACD سلبي)
653
+ if rsi_5m < 45 and macd_hist_5m < 0:
654
+ reversal_score += 0.5
655
+ # (إشارة ضعف 2: تباعد هبوطي - RSI يفشل في تسجيل قمة جديدة)
656
+ elif rsi_5m < 60 and macd_hist_5m < 0:
657
+ reversal_score += 0.3 # (إشارة ضعف متوسطة)
658
+
659
+ # --- 4. القرار ---
660
+ if reversal_score >= 0.8: # (يتطلب إشارتي ضعف قويتين)
661
+ return f"Tactical 5m Profit Save: Reversal signal detected (Score: {reversal_score})"
662
 
663
  return None
664
 
665
  except Exception as e:
666
+ print(f"❌ [Sentry] خطأ في مراقب حماية الأرباح 5m: {e}")
667
  return None
668
  # 🔴 --- END OF CHANGE --- 🔴
669
 
 
850
  await self.learning_hub.analyze_trade_and_learn(trade_to_close, reason)
851
  else: print("⚠️ [Sentry] LearningHub غير متاح، تم تخطي التعلم.")
852
 
 
853
  if self.callback_on_close:
854
  print("🔄 [Executor] Trade closed. Scheduling immediate Explorer cycle...")
855
  asyncio.create_task(self.callback_on_close())
 
856
 
857
  print(f"✅ [Executor] تم إغلاق الصفقة (الوهمية) {symbol} - السبب: {reason} - PnL: {pnl_percent:+.2f}%")
858
  return True
 
930
  except Exception as e: print(f"❌ Failed to get trade by symbol {symbol}: {e}"); return None
931
 
932
 
933
+ print(f"✅ Trade Manager loaded - V7.0 (5m Detector Profit-Saver + Post-Close Cycle) (ccxt.async_support: {CCXT_ASYNC_AVAILABLE})")