Riy777 commited on
Commit
ebf5dd4
·
1 Parent(s): fd02a1f

Update trade_manager.py

Browse files
Files changed (1) hide show
  1. trade_manager.py +28 -60
trade_manager.py CHANGED
@@ -1,4 +1,4 @@
1
- # trade_manager.py (Updated to V6.7 - 1M Indicator & Order Book Trigger)
2
  import asyncio
3
  import json
4
  import time
@@ -8,15 +8,12 @@ from datetime import datetime, timedelta
8
  from typing import Dict, Any, List
9
  from collections import deque, defaultdict
10
 
11
- # 🔴 --- START OF CHANGE (V6.7) --- 🔴
12
- # (إضافة pandas لتحليل مؤشرات 1-دقيقة)
13
  import pandas as pd
14
  try:
15
  import pandas_ta as ta
16
  except ImportError:
17
  print("⚠️ مكتبة pandas_ta غير موجودة، مؤشرات الحارس (Sentry 1m) ستفشل.")
18
  ta = None
19
- # 🔴 --- END OF CHANGE --- 🔴
20
 
21
  try:
22
  import ccxt.async_support as ccxtasync
@@ -47,16 +44,11 @@ class TacticalData:
47
  self.last_kucoin_trade_id = None
48
  self.last_confirmation_trade_ids = defaultdict(lambda: None)
49
 
50
- # 🔴 --- START OF CHANGE (V6.7) --- 🔴
51
- # (حذف: self.one_min_rsi)
52
- # (إضافة: بيانات ومؤشرات 1-دقيقة)
53
- self.ohlcv_1m = deque(maxlen=100) # (لتخزين 100 شمعة 1-دقيقة)
54
- self.indicators_1m = {} # (لتخزين EMA 9, EMA 21, MACD Hist)
55
  self.last_1m_candle_timestamp = None
56
- # 🔴 --- END OF CHANGE --- 🔴
57
 
58
  def add_trade(self, trade):
59
- """إضافة صفقة KuCoin (الأساسية)"""
60
  trade_id = trade.get('id')
61
  if trade_id and trade_id == self.last_kucoin_trade_id:
62
  return
@@ -77,7 +69,6 @@ class TacticalData:
77
  except Exception: pass
78
 
79
  def add_confirmation_trade(self, exchange_id: str, trade: Dict):
80
- """(جديد) إضافة صفقة تأكيد (Bybit, OKX, etc.)"""
81
  trade_id = trade.get('id')
82
  if trade_id and trade_id == self.last_confirmation_trade_ids[exchange_id]:
83
  return
@@ -103,43 +94,34 @@ class TacticalData:
103
  return {"bids_depth": bids_depth, "asks_depth": asks_depth}
104
  except Exception: return {"bids_depth": 0, "asks_depth": 0}
105
 
106
- # 🔴 --- START OF CHANGE (V6.7) --- 🔴
107
- # (حذف دالة get_1m_rsi التقريبية)
108
-
109
  def add_1m_ohlcv(self, ohlcv_data: List):
110
- """(جديد V6.7) إضافة شموع 1-دقيقة وحساب المؤشرات"""
111
  if not ohlcv_data:
112
  return
113
 
114
- # (إضافة الشموع الجديدة فقط)
115
  new_candles_added = False
116
  for candle in ohlcv_data:
117
  timestamp = candle[0]
118
  if timestamp and timestamp != self.last_1m_candle_timestamp:
119
  if self.ohlcv_1m and timestamp < self.ohlcv_1m[-1][0]:
120
- continue # (تجاهل الشموع القديمة إذا حدث تداخل)
121
 
122
  self.ohlcv_1m.append(candle)
123
  self.last_1m_candle_timestamp = timestamp
124
  new_candles_added = True
125
 
126
- # (حساب المؤشرات فقط إذا تغيرت البيانات)
127
- if new_candles_added and len(self.ohlcv_1m) >= 26: # (26 كافية لـ EMA 21 و MACD)
128
  self._analyze_1m_indicators()
129
 
130
  def _analyze_1m_indicators(self):
131
- """(جديد V6.7) حساب مؤشرات 1-دقيقة الحقيقية"""
132
  if ta is None or len(self.ohlcv_1m) < 26:
133
  self.indicators_1m = {}
134
  return
135
 
136
  try:
137
- # (تحويل deque إلى DataFrame للمعالجة)
138
  df = pd.DataFrame(list(self.ohlcv_1m), columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
139
  df[['open', 'high', 'low', 'close', 'volume']] = df[['open', 'high', 'low', 'close', 'volume']].astype(float)
140
  close = df['close']
141
 
142
- # (حساب المؤشرات المطلوبة)
143
  ema_9 = ta.ema(close, length=9)
144
  ema_21 = ta.ema(close, length=21)
145
  macd_data = ta.macd(close, fast=12, slow=26, signal=9)
@@ -157,29 +139,25 @@ class TacticalData:
157
  self.indicators_1m = {}
158
 
159
  except Exception as e:
160
- # print(f"⚠️ [Sentry] خطأ في حساب مؤشرات 1m لـ {self.symbol}: {e}")
161
  self.indicators_1m = {}
162
- # 🔴 --- END OF CHANGE --- 🔴
163
 
164
  def get_tactical_snapshot(self):
165
- """(محدث V6.7) لإرجاع مؤشرات 1-دقيقة الحقيقية"""
166
  agg_cvd = sum(self.confirmation_cvd.values())
167
 
168
- # 🔴 --- (تغيير V6.7) --- 🔴
169
  return {
170
  "cvd_kucoin": self.cvd,
171
  "cvd_confirmation_sources": dict(self.confirmation_cvd),
172
  "cvd_confirmation_aggregate": agg_cvd,
173
  "large_trades_count_5m": len([t for t in self.large_trades if t.get('timestamp') and (time.time() - t['timestamp']/1000) < 300]),
174
- # (حذف rsi_1m_approx)
175
- "indicators_1m": self.indicators_1m, # (إضافة المؤشرات الجديدة)
176
  "ob_analysis": self.analyze_order_book()
177
  }
178
- # 🔴 --- نهاية التغيير --- 🔴
179
 
180
 
181
  class TradeManager:
182
- def __init__(self, r2_service, learning_hub=None, data_manager=None, state_manager=None):
 
 
183
  if not CCXT_ASYNC_AVAILABLE:
184
  raise RuntimeError("مكتبة 'ccxt.async_support' غير متاحة.")
185
 
@@ -187,6 +165,12 @@ class TradeManager:
187
  self.learning_hub = learning_hub
188
  self.data_manager = data_manager
189
  self.state_manager = state_manager
 
 
 
 
 
 
190
  self.is_running = False
191
  self.sentry_watchlist = {}
192
  self.sentry_tasks = {}
@@ -354,8 +338,6 @@ class TradeManager:
354
  await asyncio.sleep(10)
355
  continue
356
 
357
- # 🔴 --- START OF CHANGE (V6.7) --- 🔴
358
- # (جلب 3 أنواع بيانات بالتوازي)
359
  tasks = {
360
  'ob': asyncio.create_task(self.kucoin_rest.fetch_order_book(symbol, limit=20)),
361
  'trades': asyncio.create_task(self.kucoin_rest.fetch_trades(symbol, since=int((time.time() - 60) * 1000), limit=50)),
@@ -365,23 +347,19 @@ class TradeManager:
365
  await asyncio.wait(tasks.values(), return_when=asyncio.ALL_COMPLETED)
366
 
367
  if symbol not in self.tactical_data_cache:
368
- continue # (ربما تم إيقاف المراقبة أثناء الجلب)
369
 
370
- # 1. معالجة دفتر الطلبات
371
  if not tasks['ob'].exception():
372
  self.tactical_data_cache[symbol].set_order_book(tasks['ob'].result())
373
 
374
- # 2. معالجة آخر الصفقات
375
  if not tasks['trades'].exception():
376
  trades = tasks['trades'].result()
377
  trades.sort(key=lambda x: x['timestamp'])
378
  for trade in trades:
379
  self.tactical_data_cache[symbol].add_trade(trade)
380
 
381
- # 3. معالجة شموع 1-دقيقة
382
  if not tasks['ohlcv_1m'].exception():
383
  self.tactical_data_cache[symbol].add_1m_ohlcv(tasks['ohlcv_1m'].result())
384
- # 🔴 --- END OF CHANGE --- 🔴
385
 
386
  await asyncio.sleep(self.polling_interval)
387
 
@@ -472,8 +450,6 @@ class TradeManager:
472
  is_still_on_watchlist = symbol in self.sentry_watchlist
473
 
474
  if is_still_on_watchlist:
475
- # 🔴 --- (تغيير V6.7) --- 🔴
476
- # (snapshot يحتوي الآن على مؤشرات 1-دقيقة الحقيقية)
477
  trigger = self._check_entry_trigger(symbol, strategy_hint, snapshot)
478
  if trigger:
479
  print(f"✅ [Sentry] زناد دخول تكتيكي لـ {symbol} (استراتيجية: {strategy_hint})")
@@ -489,11 +465,9 @@ class TradeManager:
489
  raise
490
  except Exception as e: print(f"❌ [Sentry] خطأ في حلقة التحليل التكتيكي لـ {symbol}: {e}"); traceback.print_exc()
491
 
492
- # 🔴 --- START OF CHANGE (V6.7) --- 🔴
493
  def _check_entry_trigger(self, symbol: str, strategy_hint: str, data: Dict) -> bool:
494
  """(محدث V6.7) زناد ثلاثي: CVD + دفتر الطلبات + مؤشرات 1-دقيقة"""
495
 
496
- # --- جلب البيانات ---
497
  cvd_kucoin = data.get('cvd_kucoin', 0)
498
 
499
  ob_analysis = data.get('ob_analysis', {})
@@ -504,38 +478,24 @@ class TradeManager:
504
  ema_9_1m = indicators_1m.get('ema_9')
505
  ema_21_1m = indicators_1m.get('ema_21')
506
 
507
- # (للاستراتيجيات الأخرى)
508
  large_trades = data.get('large_trades_count_5m', 0)
509
 
510
- # --- منطق الزناد ---
511
  if strategy_hint in ['breakout_momentum', 'trend_following']:
512
 
513
- # (الشرط 0: يجب أن تتوفر بيانات 1-دقيقة)
514
  if ema_9_1m is None or ema_21_1m is None:
515
  return False
516
 
517
- # (الشرط 1: زخم الصفقات - CVD)
518
  cvd_check = (cvd_kucoin > 0)
519
-
520
- # (الشرط 2: زخم دفتر الطلبات - OB Depth)
521
  ob_check = (bids_depth > asks_depth)
522
-
523
- # (الشرط 3: زخم الاتجاه - 1m EMAs)
524
  ema_check = (ema_9_1m > ema_21_1m)
525
 
526
  if cvd_check and ob_check and ema_check:
527
  print(f" [Trigger] {symbol} (Momentum): CVD+, OB+, 1m_EMA+. الدخول!")
528
  return True
529
- # (إذا فشل، لا نطبع شيئاً لتقليل التشويش)
530
 
531
  elif strategy_hint == 'mean_reversion':
532
- # (لم نطور منطق 1-دقيقة للانعكاس بعد، نستخدم المنطق القديم)
533
- # (ملاحظة: مؤشر rsi_1m_approx لم يعد موجوداً، لذا هذا الزناد معطل مؤقتاً)
534
- # rsi = data.get('rsi_1m_approx', 50) # (محذوف)
535
- # if (rsi < 35):
536
- # print(f" [Trigger] {symbol} Reversion: RSI={rsi:.1f}")
537
- # return True
538
- pass # (يبقى معطلاً حتى نضيف مؤشرات انعكاس 1-دقيقة)
539
 
540
  elif strategy_hint == 'volume_spike':
541
  if (large_trades > 0):
@@ -543,7 +503,6 @@ class TradeManager:
543
  return True
544
 
545
  return False
546
- # 🔴 --- END OF CHANGE --- 🔴
547
 
548
  def _check_exit_trigger(self, trade: Dict, data: Dict, tactical_data: TacticalData) -> str:
549
  """(محدث V6.6) يراقب وقف الخسارة وجني الأرباح باستخدام (Bid) و (Last Trade Price)"""
@@ -763,6 +722,15 @@ class TradeManager:
763
  print(f"🧠 [LearningHub] تشغيل التعلم (Reflector+Stats) لـ {symbol}...")
764
  await self.learning_hub.analyze_trade_and_learn(trade_to_close, reason)
765
  else: print("⚠️ [Sentry] LearningHub غير متاح، تم تخطي التعلم.")
 
 
 
 
 
 
 
 
 
766
  print(f"✅ [Executor] تم إغلاق الصفقة (الوهمية) {symbol} - السبب: {reason} - PnL: {pnl_percent:+.2f}%")
767
  return True
768
  except Exception as e: print(f"❌ [Executor] فشل فادح أثناء إغلاق الصفقة (الوهمية) {symbol}: {e}"); traceback.print_exc(); raise
@@ -839,4 +807,4 @@ class TradeManager:
839
  except Exception as e: print(f"❌ Failed to get trade by symbol {symbol}: {e}"); return None
840
 
841
 
842
- print(f"✅ Trade Manager loaded - V6.7 (1M Indicator & OB Trigger) (ccxt.async_support: {CCXT_ASYNC_AVAILABLE})")
 
1
+ # trade_manager.py (Updated to V6.8 - Added Post-Close Cycle Trigger)
2
  import asyncio
3
  import json
4
  import time
 
8
  from typing import Dict, Any, List
9
  from collections import deque, defaultdict
10
 
 
 
11
  import pandas as pd
12
  try:
13
  import pandas_ta as ta
14
  except ImportError:
15
  print("⚠️ مكتبة pandas_ta غير موجودة، مؤشرات الحارس (Sentry 1m) ستفشل.")
16
  ta = None
 
17
 
18
  try:
19
  import ccxt.async_support as ccxtasync
 
44
  self.last_kucoin_trade_id = None
45
  self.last_confirmation_trade_ids = defaultdict(lambda: None)
46
 
47
+ self.ohlcv_1m = deque(maxlen=100)
48
+ self.indicators_1m = {}
 
 
 
49
  self.last_1m_candle_timestamp = None
 
50
 
51
  def add_trade(self, trade):
 
52
  trade_id = trade.get('id')
53
  if trade_id and trade_id == self.last_kucoin_trade_id:
54
  return
 
69
  except Exception: pass
70
 
71
  def add_confirmation_trade(self, exchange_id: str, trade: Dict):
 
72
  trade_id = trade.get('id')
73
  if trade_id and trade_id == self.last_confirmation_trade_ids[exchange_id]:
74
  return
 
94
  return {"bids_depth": bids_depth, "asks_depth": asks_depth}
95
  except Exception: return {"bids_depth": 0, "asks_depth": 0}
96
 
 
 
 
97
  def add_1m_ohlcv(self, ohlcv_data: List):
 
98
  if not ohlcv_data:
99
  return
100
 
 
101
  new_candles_added = False
102
  for candle in ohlcv_data:
103
  timestamp = candle[0]
104
  if timestamp and timestamp != self.last_1m_candle_timestamp:
105
  if self.ohlcv_1m and timestamp < self.ohlcv_1m[-1][0]:
106
+ continue
107
 
108
  self.ohlcv_1m.append(candle)
109
  self.last_1m_candle_timestamp = timestamp
110
  new_candles_added = True
111
 
112
+ if new_candles_added and len(self.ohlcv_1m) >= 26:
 
113
  self._analyze_1m_indicators()
114
 
115
  def _analyze_1m_indicators(self):
 
116
  if ta is None or len(self.ohlcv_1m) < 26:
117
  self.indicators_1m = {}
118
  return
119
 
120
  try:
 
121
  df = pd.DataFrame(list(self.ohlcv_1m), columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
122
  df[['open', 'high', 'low', 'close', 'volume']] = df[['open', 'high', 'low', 'close', 'volume']].astype(float)
123
  close = df['close']
124
 
 
125
  ema_9 = ta.ema(close, length=9)
126
  ema_21 = ta.ema(close, length=21)
127
  macd_data = ta.macd(close, fast=12, slow=26, signal=9)
 
139
  self.indicators_1m = {}
140
 
141
  except Exception as e:
 
142
  self.indicators_1m = {}
 
143
 
144
  def get_tactical_snapshot(self):
 
145
  agg_cvd = sum(self.confirmation_cvd.values())
146
 
 
147
  return {
148
  "cvd_kucoin": self.cvd,
149
  "cvd_confirmation_sources": dict(self.confirmation_cvd),
150
  "cvd_confirmation_aggregate": agg_cvd,
151
  "large_trades_count_5m": len([t for t in self.large_trades if t.get('timestamp') and (time.time() - t['timestamp']/1000) < 300]),
152
+ "indicators_1m": self.indicators_1m,
 
153
  "ob_analysis": self.analyze_order_book()
154
  }
 
155
 
156
 
157
  class TradeManager:
158
+ # 🔴 --- START OF CHANGE (V6.8) --- 🔴
159
+ def __init__(self, r2_service, learning_hub=None, data_manager=None, state_manager=None, callback_on_close=None):
160
+ # 🔴 --- END OF CHANGE --- 🔴
161
  if not CCXT_ASYNC_AVAILABLE:
162
  raise RuntimeError("مكتبة 'ccxt.async_support' غير متاحة.")
163
 
 
165
  self.learning_hub = learning_hub
166
  self.data_manager = data_manager
167
  self.state_manager = state_manager
168
+
169
+ # 🔴 --- START OF CHANGE (V6.8) --- 🔴
170
+ # (حفظ دالة رد النداء)
171
+ self.callback_on_close = callback_on_close
172
+ # 🔴 --- END OF CHANGE --- 🔴
173
+
174
  self.is_running = False
175
  self.sentry_watchlist = {}
176
  self.sentry_tasks = {}
 
338
  await asyncio.sleep(10)
339
  continue
340
 
 
 
341
  tasks = {
342
  'ob': asyncio.create_task(self.kucoin_rest.fetch_order_book(symbol, limit=20)),
343
  'trades': asyncio.create_task(self.kucoin_rest.fetch_trades(symbol, since=int((time.time() - 60) * 1000), limit=50)),
 
347
  await asyncio.wait(tasks.values(), return_when=asyncio.ALL_COMPLETED)
348
 
349
  if symbol not in self.tactical_data_cache:
350
+ continue
351
 
 
352
  if not tasks['ob'].exception():
353
  self.tactical_data_cache[symbol].set_order_book(tasks['ob'].result())
354
 
 
355
  if not tasks['trades'].exception():
356
  trades = tasks['trades'].result()
357
  trades.sort(key=lambda x: x['timestamp'])
358
  for trade in trades:
359
  self.tactical_data_cache[symbol].add_trade(trade)
360
 
 
361
  if not tasks['ohlcv_1m'].exception():
362
  self.tactical_data_cache[symbol].add_1m_ohlcv(tasks['ohlcv_1m'].result())
 
363
 
364
  await asyncio.sleep(self.polling_interval)
365
 
 
450
  is_still_on_watchlist = symbol in self.sentry_watchlist
451
 
452
  if is_still_on_watchlist:
 
 
453
  trigger = self._check_entry_trigger(symbol, strategy_hint, snapshot)
454
  if trigger:
455
  print(f"✅ [Sentry] زناد دخول تكتيكي لـ {symbol} (استراتيجية: {strategy_hint})")
 
465
  raise
466
  except Exception as e: print(f"❌ [Sentry] خطأ في حلقة التحليل التكتيكي لـ {symbol}: {e}"); traceback.print_exc()
467
 
 
468
  def _check_entry_trigger(self, symbol: str, strategy_hint: str, data: Dict) -> bool:
469
  """(محدث V6.7) زناد ثلاثي: CVD + دفتر الطلبات + مؤشرات 1-دقيقة"""
470
 
 
471
  cvd_kucoin = data.get('cvd_kucoin', 0)
472
 
473
  ob_analysis = data.get('ob_analysis', {})
 
478
  ema_9_1m = indicators_1m.get('ema_9')
479
  ema_21_1m = indicators_1m.get('ema_21')
480
 
 
481
  large_trades = data.get('large_trades_count_5m', 0)
482
 
 
483
  if strategy_hint in ['breakout_momentum', 'trend_following']:
484
 
 
485
  if ema_9_1m is None or ema_21_1m is None:
486
  return False
487
 
 
488
  cvd_check = (cvd_kucoin > 0)
 
 
489
  ob_check = (bids_depth > asks_depth)
 
 
490
  ema_check = (ema_9_1m > ema_21_1m)
491
 
492
  if cvd_check and ob_check and ema_check:
493
  print(f" [Trigger] {symbol} (Momentum): CVD+, OB+, 1m_EMA+. الدخول!")
494
  return True
 
495
 
496
  elif strategy_hint == 'mean_reversion':
497
+ # (ملاحظة: هذا الزناد معطل مؤقتاً لعدم وجود مؤشرات انعكاس 1-دقيقة)
498
+ pass
 
 
 
 
 
499
 
500
  elif strategy_hint == 'volume_spike':
501
  if (large_trades > 0):
 
503
  return True
504
 
505
  return False
 
506
 
507
  def _check_exit_trigger(self, trade: Dict, data: Dict, tactical_data: TacticalData) -> str:
508
  """(محدث V6.6) يراقب وقف الخسارة وجني الأرباح باستخدام (Bid) و (Last Trade Price)"""
 
722
  print(f"🧠 [LearningHub] تشغيل التعلم (Reflector+Stats) لـ {symbol}...")
723
  await self.learning_hub.analyze_trade_and_learn(trade_to_close, reason)
724
  else: print("⚠️ [Sentry] LearningHub غير متاح، تم تخطي التعلم.")
725
+
726
+ # 🔴 --- START OF CHANGE (V6.8) --- 🔴
727
+ # (تشغيل الدورة الجديدة بعد الإغلاق)
728
+ if self.callback_on_close:
729
+ print("🔄 [Executor] Trade closed. Scheduling immediate Explorer cycle...")
730
+ # (استخدام create_task لتجنب الجمود (Deadlock))
731
+ asyncio.create_task(self.callback_on_close())
732
+ # 🔴 --- END OF CHANGE --- 🔴
733
+
734
  print(f"✅ [Executor] تم إغلاق الصفقة (الوهمية) {symbol} - السبب: {reason} - PnL: {pnl_percent:+.2f}%")
735
  return True
736
  except Exception as e: print(f"❌ [Executor] فشل فادح أثناء إغلاق الصفقة (الوهمية) {symbol}: {e}"); traceback.print_exc(); raise
 
807
  except Exception as e: print(f"❌ Failed to get trade by symbol {symbol}: {e}"); return None
808
 
809
 
810
+ print(f"✅ Trade Manager loaded - V6.8 (1M Trigger + Post-Close Cycle) (ccxt.async_support: {CCXT_ASYNC_AVAILABLE})")