Riy777 commited on
Commit
c994b92
·
verified ·
1 Parent(s): 6bfd228

Update ml_engine/data_manager.py

Browse files
Files changed (1) hide show
  1. ml_engine/data_manager.py +172 -77
ml_engine/data_manager.py CHANGED
@@ -1,31 +1,51 @@
1
- # ml_engine/data_manager.py
2
- # (V18.1 - GEM-Architect: Synergy Matrix + Debug Mode)
 
 
3
 
4
  import asyncio
5
  import httpx
6
  import traceback
7
- from ml_engine.processor import SystemLimits
8
- import ccxt.async_support as ccxt
9
  import logging
10
  import pandas as pd
11
  import numpy as np
12
  from typing import List, Dict, Any
13
 
14
- # إعدادات التسجيل
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  logging.getLogger("httpx").setLevel(logging.WARNING)
16
  logging.getLogger("httpcore").setLevel(logging.WARNING)
17
  logging.getLogger("ccxt").setLevel(logging.WARNING)
18
 
19
  class DataManager:
 
 
 
 
 
 
 
20
  def __init__(self, contracts_db, whale_monitor, r2_service=None):
21
  # ==================================================================
22
- # ⚙️ إعدادات التحكم
23
  # ==================================================================
24
  self.contracts_db = contracts_db or {}
25
  self.whale_monitor = whale_monitor
26
  self.r2_service = r2_service
27
 
28
- # إعداد المنصة (KuCoin)
29
  self.exchange = ccxt.kucoin({
30
  'enableRateLimit': True,
31
  'timeout': 30000,
@@ -35,94 +55,128 @@ class DataManager:
35
  self.http_client = None
36
  self.market_cache = {}
37
 
 
38
  self.BLACKLIST_TOKENS = [
39
  'USDT', 'USDC', 'DAI', 'TUSD', 'BUSD', 'FDUSD', 'EUR', 'PAX',
40
- 'UP', 'DOWN', 'BEAR', 'BULL', '3S', '3L'
41
  ]
 
 
42
 
43
  async def initialize(self):
44
- """تهيئة مدير البيانات"""
45
  print(" > [DataManager] Starting initialization...")
46
- self.http_client = httpx.AsyncClient(timeout=30.0)
47
- await self._load_markets()
48
- print(f"✅ [DataManager V18.1] Ready (Mode: Synergy Matrix + Debug).")
 
 
 
 
49
 
50
  async def _load_markets(self):
 
51
  try:
52
  if self.exchange:
53
- await self.exchange.load_markets()
 
 
54
  self.market_cache = self.exchange.markets
 
55
  except Exception as e:
56
  print(f"❌ [DataManager] Market load failed: {e}")
57
  traceback.print_exc()
58
 
59
  async def close(self):
60
- if self.http_client: await self.http_client.aclose()
61
- if self.exchange: await self.exchange.close()
 
 
 
 
62
 
63
  # ==================================================================
64
- # 🚀 R2 Compatibility
65
  # ==================================================================
66
  async def load_contracts_from_r2(self):
 
67
  if not self.r2_service: return
68
  try:
69
  self.contracts_db = await self.r2_service.load_contracts_db_async()
70
- except Exception:
 
 
71
  self.contracts_db = {}
72
 
73
  def get_contracts_db(self) -> Dict[str, Any]:
 
74
  return self.contracts_db
75
 
76
  # ==================================================================
77
- # 🛡️ Layer 1: Synergy Screening (Trend + Depth + Logic)
78
  # ==================================================================
79
  async def layer1_rapid_screening(self) -> List[Dict[str, Any]]:
80
  """
81
- الفلترة الذكية المترابطة:
82
- لا نقبل المؤشرات منفصلة، بل نقيس التوافق بين:
83
- 1. اتجاه المتوسطات (EMA).
84
- 2. دعم دفتر الطلبات (Depth).
85
- 3. مناطق الدخول (RSI).
86
  """
87
  print(f"🔍 [Layer 1] Initiating Synergy Matrix Screening...")
88
 
89
- # 1. المرحلة 0: فلتر الكون السريع
90
  initial_candidates = await self._stage0_universe_filter()
91
- if not initial_candidates: return []
 
 
 
92
 
93
- # 2. جلب البيانات ارت + عمق)
94
- # نأخذ أفضل 150 لضمان الجودة
95
  top_liquid_candidates = initial_candidates[:150]
96
  enriched_data = await self._fetch_technical_and_depth_batch(top_liquid_candidates)
97
 
98
- # 3. حساب نقاط التوافق (Synergy Score)
99
  scored_candidates = []
100
 
101
  for item in enriched_data:
 
102
  affinity_result = self._calculate_synergy_score(item)
103
 
104
- # ✅ [DEBUG PRINT ADDED] طباعة تشخيصية لمعرفة النقاط
105
- if affinity_result['score'] > 5:
106
- print(f" 👀 Debug: {item['symbol']:<8} -> Score: {affinity_result['score']:.1f} | Tags: {affinity_result['tags']}")
107
-
108
  if affinity_result['is_eligible']:
109
  item['l1_score'] = affinity_result['score']
110
  item['tags'] = affinity_result['tags']
111
  scored_candidates.append(item)
112
 
113
- print(f" -> [L1 Logic] Candidates passing Matrix: {len(scored_candidates)}")
114
-
115
  # 4. الترتيب واختيار النخبة
 
116
  scored_candidates.sort(key=lambda x: x['l1_score'], reverse=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  final_selection = scored_candidates[:40]
118
 
 
119
  cleaned_selection = []
120
  for item in final_selection:
121
  cleaned_selection.append({
122
  'symbol': item['symbol'],
123
  'quote_volume': item.get('quote_volume', 0),
124
  'current_price': item.get('current_price', 0),
125
- 'type': ','.join(item.get('tags', [])),
126
  'l1_score': item.get('l1_score', 0)
127
  })
128
 
@@ -130,22 +184,32 @@ class DataManager:
130
  return cleaned_selection
131
 
132
  # ------------------------------------------------------------------
133
- # Stage 0: Universe Filter
134
  # ------------------------------------------------------------------
135
  async def _stage0_universe_filter(self) -> List[Dict[str, Any]]:
 
 
 
 
 
 
136
  try:
137
  tickers = await self.exchange.fetch_tickers()
138
  candidates = []
139
 
140
  for symbol, ticker in tickers.items():
 
141
  if not symbol.endswith('/USDT'): continue
142
 
 
143
  base_curr = symbol.split('/')[0]
144
  if any(bad in base_curr for bad in self.BLACKLIST_TOKENS): continue
145
 
 
146
  quote_vol = ticker.get('quoteVolume')
147
- if not quote_vol or quote_vol < 300_000: continue
148
 
 
149
  last_price = ticker.get('last')
150
  if not last_price or last_price < 0.0001: continue
151
 
@@ -156,6 +220,7 @@ class DataManager:
156
  'change_24h': ticker.get('percentage', 0.0)
157
  })
158
 
 
159
  candidates.sort(key=lambda x: x['quote_volume'], reverse=True)
160
  return candidates
161
 
@@ -164,133 +229,156 @@ class DataManager:
164
  return []
165
 
166
  # ------------------------------------------------------------------
167
- # Data Fetching Helpers
168
  # ------------------------------------------------------------------
169
  async def _fetch_technical_and_depth_batch(self, candidates: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
170
- chunk_size = 10
 
 
 
 
171
  results = []
 
172
  for i in range(0, len(candidates), chunk_size):
173
  chunk = candidates[i:i + chunk_size]
 
 
174
  chunk_tasks = [self._fetch_single_full_data(c) for c in chunk]
 
 
175
  chunk_results = await asyncio.gather(*chunk_tasks)
 
 
176
  results.extend([r for r in chunk_results if r is not None])
 
 
177
  await asyncio.sleep(0.1)
 
178
  return results
179
 
180
  async def _fetch_single_full_data(self, candidate: Dict[str, Any]) -> Any:
 
181
  symbol = candidate['symbol']
182
  try:
183
- # نحتاج 200 شمعة لحساب EMA 200 بدقة
184
  ohlcv_task = self.exchange.fetch_ohlcv(symbol, '1h', limit=205)
185
- # Order Book Snapshot
 
186
  ob_task = self.exchange.fetch_order_book(symbol, limit=20)
187
 
 
188
  ohlcv_1h, order_book = await asyncio.gather(ohlcv_task, ob_task)
189
 
190
- if not ohlcv_1h or len(ohlcv_1h) < 200: # نحتاج بيانات كافية للمتوسطات
191
- return None
 
192
 
193
  candidate['ohlcv_1h'] = ohlcv_1h
194
  candidate['order_book_snapshot'] = order_book
195
  return candidate
 
196
  except Exception:
 
197
  return None
198
 
199
  # ------------------------------------------------------------------
200
- # 🧠 The Logic Core: Synergy Matrix Scoring (Relationships)
201
  # ------------------------------------------------------------------
202
  def _calculate_synergy_score(self, data: Dict[str, Any]) -> Dict[str, Any]:
203
  """
204
- مصفوفة التوافق:
205
- نقوم بحساب النقاط بناءً على العلاقات بين المؤشرات وليس القيم المجردة.
 
206
  """
207
  try:
 
208
  df = pd.DataFrame(data['ohlcv_1h'], columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
209
  ob = data.get('order_book_snapshot', {})
210
 
211
  df['close'] = df['close'].astype(float)
212
  df['volume'] = df['volume'].astype(float)
213
 
214
- # --- 1. الأساسيات (Indicators) ---
215
- # EMA (Exponential Moving Average) - العمود الفقري
 
216
  df['ema50'] = df['close'].ewm(span=50, adjust=False).mean()
217
  df['ema200'] = df['close'].ewm(span=200, adjust=False).mean()
218
 
219
- # RSI
220
  delta = df['close'].diff()
221
  gain = (delta.where(delta > 0, 0)).rolling(14).mean()
222
  loss = (-delta.where(delta < 0, 0)).rolling(14).mean()
223
  rs = gain / loss
224
  df['rsi'] = 100 - (100 / (1 + rs))
225
 
226
- # Volume & Bands
227
  df['vol_ma'] = df['volume'].rolling(20).mean()
228
- curr = df.iloc[-1]
229
 
 
230
  score = 0.0
231
  tags = []
232
 
233
- # --- 2. تحليل دفتر الطلبات (The King) ---
 
234
  depth_ratio = 1.0
235
  total_bids = 0
236
  if ob and 'bids' in ob and 'asks' in ob:
237
  total_bids = sum([b[1] for b in ob['bids']])
238
  total_asks = sum([a[1] for a in ob['asks']])
239
- if total_asks > 0: depth_ratio = total_bids / total_asks
 
240
 
241
- # --- 3. مصفوفة العلاقات (Relationships Logic) ---
242
 
243
  close = curr['close']
244
  ema50 = curr['ema50']
245
  ema200 = curr['ema200']
246
  rsi = curr['rsi']
247
 
248
- # الحالة A: الثور المثالي (Trend + Support)
249
- # السعر فوق المتوسطات + يوجد دعم طلبات
250
  if close > ema50 and close > ema200:
251
  base_trend = 20
252
- if depth_ratio > 1.1: # توافق: اتجاه صاعد + سيولة شراء
253
- score += (base_trend * 1.5) # بونص 50% للتوافق
254
  tags.append("ConfirmedTrend")
255
- elif depth_ratio < 0.8: # تناقض: اتجاه صاعد لكن تفريغ
256
- score -= 20 # فخ!
257
  tags.append("BullTrap⚠️")
258
  else:
259
  score += base_trend # اتجاه صاعد عادي
260
 
261
- # الحالة B: الارتداد الذكي (Reversal + Whale Catch)
262
- # السعر تحت المتوسطات (سلبي) لكن الدفتر قوي جداً
263
  elif close < ema50:
264
- if depth_ratio > 1.5 and rsi < 35: # حيتان يشترون الهبوط بقوة
265
  score += 40 # نغفر سلبية المتوسط بسبب قوة الدفتر
266
  tags.append("WhaleNet🔥")
267
  elif depth_ratio > 1.2:
268
  score += 15
269
  tags.append("SupprortBuild")
270
  else:
271
- score -= 30 # هبوط بدون دعم = انهيار (Downtrend)
272
 
273
- # الحالة C: الزخم (Momentum)
274
- # توافق RSI مع الفوليوم
275
  rvol = curr['volume'] / curr['vol_ma'] if curr['vol_ma'] > 0 else 1.0
276
 
277
  if rsi > 50 and rsi < 75:
278
- if rvol > 1.2: # زخم سعري + حجم تداول
279
  score += 15
280
  tags.append("MomVol")
281
  else:
282
- score += 5 # زخم ضعيف
283
 
284
- # حالة خاصة: الارتفاع الجنوني (Parabolic)
285
- # السعر بعيد جداً عن المتوسطات
286
  dist_from_ema = (close - ema50) / ema50
287
  if dist_from_ema > 0.20: # 20% فوق المتوسط
288
- score -= 10 # خطورة تصحيح، نقلل النقاط قليلاً للحذر
289
  tags.append("OverExtended")
290
-
291
- # --- 4. الخلاصة ---
292
- # شرط القبول: يجب أن تكون النقاط إيجابية وتتجاوز العتبة
293
 
 
294
  return {
295
  'is_eligible': score > SystemLimits.L1_MIN_AFFINITY_SCORE,
296
  'score': score,
@@ -298,26 +386,33 @@ class DataManager:
298
  }
299
 
300
  except Exception as e:
301
- # print(f"Scoring Error: {e}")
302
  return {'is_eligible': False, 'score': 0, 'tags': []}
303
 
304
  # ==================================================================
305
- # 🎯 Helpers
306
  # ==================================================================
307
  async def get_latest_price_async(self, symbol: str) -> float:
 
308
  try:
309
  ticker = await self.exchange.fetch_ticker(symbol)
310
  return float(ticker['last'])
311
  except Exception: return 0.0
312
 
313
  async def get_latest_ohlcv(self, symbol: str, timeframe: str = '5m', limit: int = 100) -> List[List[float]]:
 
314
  try:
315
  candles = await self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
316
  return candles or []
317
  except Exception: return []
318
 
319
  async def get_order_book_snapshot(self, symbol: str, limit: int = 20) -> Dict[str, Any]:
 
320
  try:
321
  ob = await self.exchange.fetch_order_book(symbol, limit)
322
  return ob
323
- except Exception: return {}
 
 
 
 
 
1
+ # ============================================================
2
+ # 📂 ml_engine/data_manager.py
3
+ # (V18.2 - GEM-Architect: Synergy Matrix + Full Verbose Mode)
4
+ # ============================================================
5
 
6
  import asyncio
7
  import httpx
8
  import traceback
 
 
9
  import logging
10
  import pandas as pd
11
  import numpy as np
12
  from typing import List, Dict, Any
13
 
14
+ # ✅ استيراد إعدادات النظام المركزية (للربط مع Processor)
15
+ try:
16
+ from ml_engine.processor import SystemLimits
17
+ except ImportError:
18
+ # قيم افتراضية في حال فشل الاستيراد لتجنب توقف النظام
19
+ class SystemLimits:
20
+ L1_MIN_AFFINITY_SCORE = 10.0
21
+
22
+ # ✅ استيراد مكتبة التداول
23
+ import ccxt.async_support as ccxt
24
+
25
+ # ============================================================
26
+ # 📝 إعدادات التسجيل (Logging Configuration)
27
+ # ============================================================
28
  logging.getLogger("httpx").setLevel(logging.WARNING)
29
  logging.getLogger("httpcore").setLevel(logging.WARNING)
30
  logging.getLogger("ccxt").setLevel(logging.WARNING)
31
 
32
  class DataManager:
33
+ """
34
+ DataManager V18.2
35
+ -----------------
36
+ المسؤول عن جلب البيانات من المنصة، فلترتها، وتجهيزها للمعالج.
37
+ يستخدم الآن منطق 'Synergy Matrix' لدمج التحليل الفني مع عمق السوق.
38
+ """
39
+
40
  def __init__(self, contracts_db, whale_monitor, r2_service=None):
41
  # ==================================================================
42
+ # ⚙️ إعدادات التحكم والتهيئة
43
  # ==================================================================
44
  self.contracts_db = contracts_db or {}
45
  self.whale_monitor = whale_monitor
46
  self.r2_service = r2_service
47
 
48
+ # إعداد عميل المنصة (KuCoin) مع تفعيل حدود السرعة
49
  self.exchange = ccxt.kucoin({
50
  'enableRateLimit': True,
51
  'timeout': 30000,
 
55
  self.http_client = None
56
  self.market_cache = {}
57
 
58
+ # 🚫 قوائم الاستبعاد (العملات المستقرة، الرافعة، والعملات المحظورة)
59
  self.BLACKLIST_TOKENS = [
60
  'USDT', 'USDC', 'DAI', 'TUSD', 'BUSD', 'FDUSD', 'EUR', 'PAX',
61
+ 'UP', 'DOWN', 'BEAR', 'BULL', '3S', '3L', 'USDD', 'USDP'
62
  ]
63
+
64
+ print(f"📦 [DataManager] Instance Created. Blacklist items: {len(self.BLACKLIST_TOKENS)}")
65
 
66
  async def initialize(self):
67
+ """تهيئة مدير البيانات والاتصالات"""
68
  print(" > [DataManager] Starting initialization...")
69
+ try:
70
+ self.http_client = httpx.AsyncClient(timeout=30.0)
71
+ await self._load_markets()
72
+ print(f"✅ [DataManager V18.2] Ready (Mode: Synergy Matrix | Trend + Depth).")
73
+ except Exception as e:
74
+ print(f"❌ [DataManager] Init Error: {e}")
75
+ traceback.print_exc()
76
 
77
  async def _load_markets(self):
78
+ """تحميل أزواج التداول وحفظها في الذاكرة"""
79
  try:
80
  if self.exchange:
81
+ # التأكد من عدم تحميل الأسواق مرتين إذا كانت محملة
82
+ if not self.exchange.markets:
83
+ await self.exchange.load_markets()
84
  self.market_cache = self.exchange.markets
85
+ # print(f" -> [DataManager] Markets loaded: {len(self.market_cache)} pairs.")
86
  except Exception as e:
87
  print(f"❌ [DataManager] Market load failed: {e}")
88
  traceback.print_exc()
89
 
90
  async def close(self):
91
+ """إغلاق الاتصالات بأمان"""
92
+ if self.http_client:
93
+ await self.http_client.aclose()
94
+ if self.exchange:
95
+ await self.exchange.close()
96
+ print("🛑 [DataManager] Connections Closed.")
97
 
98
  # ==================================================================
99
+ # 🚀 R2 Compatibility (Integration Methods)
100
  # ==================================================================
101
  async def load_contracts_from_r2(self):
102
+ """تحميل قاعدة بيانات العقود من خدمة R2"""
103
  if not self.r2_service: return
104
  try:
105
  self.contracts_db = await self.r2_service.load_contracts_db_async()
106
+ print(f" -> [DataManager] Contracts DB updated from R2: {len(self.contracts_db)} records.")
107
+ except Exception as e:
108
+ print(f"⚠️ [DataManager] R2 Load Warning: {e}")
109
  self.contracts_db = {}
110
 
111
  def get_contracts_db(self) -> Dict[str, Any]:
112
+ """إرجاع قاعدة البيانات الحالية"""
113
  return self.contracts_db
114
 
115
  # ==================================================================
116
+ # 🛡️ Layer 1: Synergy Screening (The Core Logic)
117
  # ==================================================================
118
  async def layer1_rapid_screening(self) -> List[Dict[str, Any]]:
119
  """
120
+ الفلترة الذكية المترابطة (Synergy Matrix):
121
+ تقوم هذه الدالة بمسح السوق بالكامل، واختيار العملات التي تحقق توافقاً بين:
122
+ 1. اتجاه المتوسطات (EMA Trend).
123
+ 2. الدعم المؤسساتي في دفتر الطلبات (Order Book Depth).
124
+ 3. مناطق الدخول الذكية (Smart RSI).
125
  """
126
  print(f"🔍 [Layer 1] Initiating Synergy Matrix Screening...")
127
 
128
+ # 1. المرحلة 0: فلتر الكون (استبعاد العملات الميتة)
129
  initial_candidates = await self._stage0_universe_filter()
130
+
131
+ if not initial_candidates:
132
+ print("⚠️ [Layer 1] No candidates passed Stage 0.")
133
+ return []
134
 
135
+ # 2. جلب البيانات الفنية + عمق السوق (Batch Fetching)
136
+ # نأخذ أفضل 150 عملة لضمان جودة البيانات وعدم تجاوز حدود API
137
  top_liquid_candidates = initial_candidates[:150]
138
  enriched_data = await self._fetch_technical_and_depth_batch(top_liquid_candidates)
139
 
140
+ # 3. حساب "نقاط التوافق" (Synergy Score)
141
  scored_candidates = []
142
 
143
  for item in enriched_data:
144
+ # استدعاء دالة التقييم الجديدة
145
  affinity_result = self._calculate_synergy_score(item)
146
 
147
+ # إذا حققت الحد الأدنى من النقاط، نضيفها للقائمة
 
 
 
148
  if affinity_result['is_eligible']:
149
  item['l1_score'] = affinity_result['score']
150
  item['tags'] = affinity_result['tags']
151
  scored_candidates.append(item)
152
 
 
 
153
  # 4. الترتيب واختيار النخبة
154
+ # ترتيب تنازلي حسب النقاط
155
  scored_candidates.sort(key=lambda x: x['l1_score'], reverse=True)
156
+
157
+ # ✅ [GEM-Architect Debug] طباعة أفضل 10 عملات فقط بشكل أنيق للمراقبة
158
+ if len(scored_candidates) > 0:
159
+ print("-" * 75)
160
+ print(f"{'SYMBOL':<12} | {'SCORE':<6} | {'STRATEGY TAGS (REASON)'}")
161
+ print("-" * 75)
162
+ for i, item in enumerate(scored_candidates[:10]):
163
+ tags_str = ", ".join(item.get('tags', []))
164
+ print(f"{item['symbol']:<12} | {item['l1_score']:<6.1f} | {tags_str}")
165
+ print("-" * 75)
166
+
167
+ print(f" -> [L1 Logic] Candidates passing Matrix: {len(scored_candidates)}")
168
+
169
+ # نختار أفضل 40 عملة فقط لتمريرها للمعالجة العميقة (L2/L3)
170
  final_selection = scored_candidates[:40]
171
 
172
+ # تنظيف البيانات لتمرير ما يهم المعالج فقط
173
  cleaned_selection = []
174
  for item in final_selection:
175
  cleaned_selection.append({
176
  'symbol': item['symbol'],
177
  'quote_volume': item.get('quote_volume', 0),
178
  'current_price': item.get('current_price', 0),
179
+ 'type': ','.join(item.get('tags', [])), # تمرير التاغات كسترينغ
180
  'l1_score': item.get('l1_score', 0)
181
  })
182
 
 
184
  return cleaned_selection
185
 
186
  # ------------------------------------------------------------------
187
+ # Stage 0: Universe Filter (Basic Liquidity Check)
188
  # ------------------------------------------------------------------
189
  async def _stage0_universe_filter(self) -> List[Dict[str, Any]]:
190
+ """
191
+ فلتر أولي سريع جداً لاستبعاد:
192
+ - العملات ذات السيولة المعدومة (أقل من 300 ألف دولار).
193
+ - العملات المستقرة والمحظورة.
194
+ - العملات ذات السعر الصفري تقريباً (أخطاء بيانات).
195
+ """
196
  try:
197
  tickers = await self.exchange.fetch_tickers()
198
  candidates = []
199
 
200
  for symbol, ticker in tickers.items():
201
+ # نقبل فقط أزواج USDT
202
  if not symbol.endswith('/USDT'): continue
203
 
204
+ # التحقق من القائمة السوداء
205
  base_curr = symbol.split('/')[0]
206
  if any(bad in base_curr for bad in self.BLACKLIST_TOKENS): continue
207
 
208
+ # شرط السيولة (Quote Volume)
209
  quote_vol = ticker.get('quoteVolume')
210
+ if not quote_vol or quote_vol < 300_000: continue # Min 300k USDT
211
 
212
+ # شرط السعر المنطقي
213
  last_price = ticker.get('last')
214
  if not last_price or last_price < 0.0001: continue
215
 
 
220
  'change_24h': ticker.get('percentage', 0.0)
221
  })
222
 
223
+ # الترتيب حسب السيولة لضمان أننا نفحص الأقوى أولاً
224
  candidates.sort(key=lambda x: x['quote_volume'], reverse=True)
225
  return candidates
226
 
 
229
  return []
230
 
231
  # ------------------------------------------------------------------
232
+ # Data Fetching Helpers (Updated for Depth & Parallel Execution)
233
  # ------------------------------------------------------------------
234
  async def _fetch_technical_and_depth_batch(self, candidates: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
235
+ """
236
+ جلب البيانات الفنية (OHLCV) + دفتر الطلبات (Order Book) بالتوازي.
237
+ يتم تقسيم العملات إلى دفعات (Chunks) لتجنب الحظر.
238
+ """
239
+ chunk_size = 10 # عدد العملات في كل دفعة
240
  results = []
241
+
242
  for i in range(0, len(candidates), chunk_size):
243
  chunk = candidates[i:i + chunk_size]
244
+
245
+ # إنشاء مهام غير متزامنة لكل عملة في الدفعة
246
  chunk_tasks = [self._fetch_single_full_data(c) for c in chunk]
247
+
248
+ # تنفيذ المهام وانتظار النتائج
249
  chunk_results = await asyncio.gather(*chunk_tasks)
250
+
251
+ # تجميع النتائج الصالحة فقط
252
  results.extend([r for r in chunk_results if r is not None])
253
+
254
+ # استراحة قصيرة جداً لتخفيف الحمل على الذاكرة والشبكة
255
  await asyncio.sleep(0.1)
256
+
257
  return results
258
 
259
  async def _fetch_single_full_data(self, candidate: Dict[str, Any]) -> Any:
260
+ """جلب بيانات عملة واحدة (شارت + عمق)"""
261
  symbol = candidate['symbol']
262
  try:
263
+ # 1. طلب الشموع (OHLCV) - نحتاج 200 شمعة لحساب المتوسطات الطويلة (EMA 200)
264
  ohlcv_task = self.exchange.fetch_ohlcv(symbol, '1h', limit=205)
265
+
266
+ # 2. طلب دفتر الطلبات (Order Book) - لقطة لأفضل 20 مستوى فقط
267
  ob_task = self.exchange.fetch_order_book(symbol, limit=20)
268
 
269
+ # تنفيذ الطلبين معاً
270
  ohlcv_1h, order_book = await asyncio.gather(ohlcv_task, ob_task)
271
 
272
+ # التحقق من جودة البيانات
273
+ if not ohlcv_1h or len(ohlcv_1h) < 200:
274
+ return None # بيانات غير كافية للتحليل
275
 
276
  candidate['ohlcv_1h'] = ohlcv_1h
277
  candidate['order_book_snapshot'] = order_book
278
  return candidate
279
+
280
  except Exception:
281
+ # في حال فشل الجلب (Timeout أو غيره)، نتجاهل العملة
282
  return None
283
 
284
  # ------------------------------------------------------------------
285
+ # 🧠 The Logic Core: Synergy Matrix Scoring (New V18.2 Logic)
286
  # ------------------------------------------------------------------
287
  def _calculate_synergy_score(self, data: Dict[str, Any]) -> Dict[str, Any]:
288
  """
289
+ مصفوفة التوافق (Synergy Matrix):
290
+ تقوم بحساب النقاط بناءً على العلاقات المترابطة بين المؤشرات.
291
+ لا تعتمد على القيم المطلقة فقط، بل على سياق السوق.
292
  """
293
  try:
294
+ # تحويل البيانات إلى DataFrame للتحليل
295
  df = pd.DataFrame(data['ohlcv_1h'], columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
296
  ob = data.get('order_book_snapshot', {})
297
 
298
  df['close'] = df['close'].astype(float)
299
  df['volume'] = df['volume'].astype(float)
300
 
301
+ # --- 1. حساب المؤشرات الفنية الأساسية ---
302
+
303
+ # EMA (العمود الفقري للاتجاه)
304
  df['ema50'] = df['close'].ewm(span=50, adjust=False).mean()
305
  df['ema200'] = df['close'].ewm(span=200, adjust=False).mean()
306
 
307
+ # RSI (مؤشر الزخم)
308
  delta = df['close'].diff()
309
  gain = (delta.where(delta > 0, 0)).rolling(14).mean()
310
  loss = (-delta.where(delta < 0, 0)).rolling(14).mean()
311
  rs = gain / loss
312
  df['rsi'] = 100 - (100 / (1 + rs))
313
 
314
+ # Volume MA (متوسط الحجم)
315
  df['vol_ma'] = df['volume'].rolling(20).mean()
 
316
 
317
+ curr = df.iloc[-1]
318
  score = 0.0
319
  tags = []
320
 
321
+ # --- 2. تحليل دفتر الطلبات (Macro Order Book) ---
322
+ # حساب نسبة ضغط الشراء مقابل البيع (Bid/Ask Ratio)
323
  depth_ratio = 1.0
324
  total_bids = 0
325
  if ob and 'bids' in ob and 'asks' in ob:
326
  total_bids = sum([b[1] for b in ob['bids']])
327
  total_asks = sum([a[1] for a in ob['asks']])
328
+ if total_asks > 0:
329
+ depth_ratio = total_bids / total_asks
330
 
331
+ # --- 3. مصفوفة العلاقات (Logic Execution) ---
332
 
333
  close = curr['close']
334
  ema50 = curr['ema50']
335
  ema200 = curr['ema200']
336
  rsi = curr['rsi']
337
 
338
+ # الحالة A: الثور المؤكد (Confirmed Trend)
339
+ # السعر فوق المتوسطات + يوجد دعم حقيقي في الدفتر
340
  if close > ema50 and close > ema200:
341
  base_trend = 20
342
+ if depth_ratio > 1.1: # توافق إيجابي
343
+ score += (base_trend * 1.5)
344
  tags.append("ConfirmedTrend")
345
+ elif depth_ratio < 0.8: # تناقض (سعر صاعد لكن الحيتان يبيعون)
346
+ score -= 20
347
  tags.append("BullTrap⚠️")
348
  else:
349
  score += base_trend # اتجاه صاعد عادي
350
 
351
+ # الحالة B: صيد الحيتان / الارتداد (Whale Net)
352
+ # السعر تحت المتوسطات (هابط) لكن الدفتر يظهر شراء عنيف
353
  elif close < ema50:
354
+ if depth_ratio > 1.5 and rsi < 35: # شراء قوي جداً عند القاع
355
  score += 40 # نغفر سلبية المتوسط بسبب قوة الدفتر
356
  tags.append("WhaleNet🔥")
357
  elif depth_ratio > 1.2:
358
  score += 15
359
  tags.append("SupprortBuild")
360
  else:
361
+ score -= 30 # هبوط بدون دعم = انهيار
362
 
363
+ # الحالة C: الزخم والحجم (Momentum & Volume)
364
+ # حساب الحجم النسبي (Relative Volume)
365
  rvol = curr['volume'] / curr['vol_ma'] if curr['vol_ma'] > 0 else 1.0
366
 
367
  if rsi > 50 and rsi < 75:
368
+ if rvol > 1.2: # زخم سعري مع سيولة
369
  score += 15
370
  tags.append("MomVol")
371
  else:
372
+ score += 5
373
 
374
+ # حالة خاصة: الامتداد السعري المفرط (Over Extension)
375
+ # السعر ابتعد كثيراً عن المتوسط (خطر التصحيح)
376
  dist_from_ema = (close - ema50) / ema50
377
  if dist_from_ema > 0.20: # 20% فوق المتوسط
378
+ score -= 10
379
  tags.append("OverExtended")
 
 
 
380
 
381
+ # إرجاع النتيجة
382
  return {
383
  'is_eligible': score > SystemLimits.L1_MIN_AFFINITY_SCORE,
384
  'score': score,
 
386
  }
387
 
388
  except Exception as e:
389
+ # في حال حدوث خطأ حسابي، نرفض العملة للأمان
390
  return {'is_eligible': False, 'score': 0, 'tags': []}
391
 
392
  # ==================================================================
393
+ # 🎯 Public Helpers (Legacy & Utility Methods)
394
  # ==================================================================
395
  async def get_latest_price_async(self, symbol: str) -> float:
396
+ """جلب آخر سعر للعملة"""
397
  try:
398
  ticker = await self.exchange.fetch_ticker(symbol)
399
  return float(ticker['last'])
400
  except Exception: return 0.0
401
 
402
  async def get_latest_ohlcv(self, symbol: str, timeframe: str = '5m', limit: int = 100) -> List[List[float]]:
403
+ """جلب بيانات الشموع (OHLCV)"""
404
  try:
405
  candles = await self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
406
  return candles or []
407
  except Exception: return []
408
 
409
  async def get_order_book_snapshot(self, symbol: str, limit: int = 20) -> Dict[str, Any]:
410
+ """جلب لقطة سريعة لدفتر الطلبات"""
411
  try:
412
  ob = await self.exchange.fetch_order_book(symbol, limit)
413
  return ob
414
+ except Exception: return {}
415
+
416
+ def get_supported_timeframes(self):
417
+ """إرجاع الإطارات الزمنية المدعومة"""
418
+ return list(self.exchange.timeframes.keys()) if self.exchange else []