Riy777 commited on
Commit
de7e115
·
verified ·
1 Parent(s): eaa2c8d

Update ml_engine/data_manager.py

Browse files
Files changed (1) hide show
  1. ml_engine/data_manager.py +175 -251
ml_engine/data_manager.py CHANGED
@@ -1,6 +1,6 @@
1
  # ============================================================
2
  # 📂 ml_engine/data_manager.py
3
- # (V18.3 - GEM-Architect: Adaptive Synergy + Rescue Mode)
4
  # ============================================================
5
 
6
  import asyncio
@@ -9,32 +9,30 @@ 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.3 (Adaptive)
35
- ----------------------------
36
- المسؤول عن جلب البيانات من المنصة، فلترتها، وتجهيزها للمعالج.
37
- يتميز بنظام 'Synergy Matrix' المرن الذي يتكيف مع ظروف السوق (Rescue Mode).
38
  """
39
 
40
  def __init__(self, contracts_db, whale_monitor, r2_service=None):
@@ -61,16 +59,15 @@ class DataManager:
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
- # ✅ GEM-Architect: زيادة الـ Timeout لتجنب مشاكل الشبكة أثناء الضغط
71
  self.http_client = httpx.AsyncClient(timeout=60.0)
72
  await self._load_markets()
73
- print(f"✅ [DataManager V18.3] Ready (Mode: Synergy Matrix | Adaptive 100).")
74
  except Exception as e:
75
  print(f"❌ [DataManager] Init Error: {e}")
76
  traceback.print_exc()
@@ -83,7 +80,6 @@ class DataManager:
83
  if not self.exchange.markets:
84
  await self.exchange.load_markets()
85
  self.market_cache = self.exchange.markets
86
- # print(f" -> [DataManager] Markets loaded: {len(self.market_cache)} pairs.")
87
  except Exception as e:
88
  print(f"❌ [DataManager] Market load failed: {e}")
89
  traceback.print_exc()
@@ -97,10 +93,9 @@ class DataManager:
97
  print("🛑 [DataManager] Connections Closed.")
98
 
99
  # ==================================================================
100
- # 🚀 R2 Compatibility (Integration Methods)
101
  # ==================================================================
102
  async def load_contracts_from_r2(self):
103
- """تحميل قاعدة بيانات العقود من خدمة R2"""
104
  if not self.r2_service: return
105
  try:
106
  self.contracts_db = await self.r2_service.load_contracts_db_async()
@@ -110,132 +105,102 @@ class DataManager:
110
  self.contracts_db = {}
111
 
112
  def get_contracts_db(self) -> Dict[str, Any]:
113
- """إرجاع قاعدة البيانات الحالية"""
114
  return self.contracts_db
115
-
116
  # ==================================================================
117
- # 🛡️ Layer 1: Synergy Screening (GEM-Architect Fix: Adaptive & Debuggable)
118
  # ==================================================================
119
  async def layer1_rapid_screening(self) -> List[Dict[str, Any]]:
120
  """
121
- الفلترة الذكية (نسخة مصححة): تتكيف مع ظروف السوق وتعالج مشاكل البيانات.
122
- تقوم بمسح أفضل 100 عملة وتفعيل وضع الإنقاذ إذا كانت النتائج صفرية.
123
  """
124
- print(f"🔍 [Layer 1] Initiating Synergy Matrix Screening...")
 
 
 
125
 
126
- # 1. المرحلة 0: فلتر الكون (استبعاد العملات الميتة)
127
- initial_candidates = await self._stage0_universe_filter()
128
 
129
- if not initial_candidates:
130
- print("⚠️ [Layer 1] No candidates passed Stage 0 (Check Internet/API).")
 
 
131
  return []
132
 
133
- # GEM-Architect Update: فحص أفضل 100 عملة حسب الطلب
134
- # تم تحديد العدد لضمان التوازن بين كفاءة المسح وتجنب الحظر
135
- top_liquid_candidates = initial_candidates[:100]
136
 
137
- print(f" -> Fetching deep data for top {len(top_liquid_candidates)} assets...")
138
- enriched_data = await self._fetch_technical_and_depth_batch(top_liquid_candidates)
 
 
 
 
 
 
 
 
139
 
140
- # 🚨 Debug Check: هل البيانات تصل فعلاً؟
141
- if len(enriched_data) == 0:
142
- print("❌ [CRITICAL] 0 assets returned data. API might be throttling or returning empty responses.")
143
- return []
144
 
145
- # 3. حساب النقاط (Synergy Score)
146
- scored_candidates = []
147
- debug_scores = [] # لتسجيل وتتبع لماذا تفشل العملات
148
 
149
- # استخدام حد أدنى مرن (Adaptive Threshold)
150
- current_threshold = SystemLimits.L1_MIN_AFFINITY_SCORE
151
-
152
  for item in enriched_data:
153
- # استدعاء دالة التقييم الجديدة
154
- affinity_result = self._calculate_synergy_score(item)
155
- score = affinity_result['score']
156
- item['l1_score'] = score
157
- item['tags'] = affinity_result['tags']
158
-
159
- debug_scores.append(score)
160
-
161
- # إذا حققت الحد الأدنى من النقاط، نضيفها للقائمة
162
- if score >= current_threshold:
163
- scored_candidates.append(item)
164
-
165
- # 4. 🔥 نظام الإنقاذ (Rescue Mode)
166
- # إذا لم نجد أي عملة بالفلتر القاسي، نخفض المعايير فوراً بدلاً من إرجاع قائمة فارغة
167
- if len(scored_candidates) == 0 and len(enriched_data) > 0:
168
- max_found_score = max(debug_scores) if debug_scores else 0
169
- print(f"⚠️ [Layer 1] Too strict! Max score found was {max_found_score:.2f}. Activating Rescue Mode...")
170
-
171
- # إعادة المحاولة بحد أدنى 0 (أي عملة إيجابية تقنياً)
172
- for item in enriched_data:
173
- if item['l1_score'] > 0:
174
- scored_candidates.append(item)
175
 
176
- if scored_candidates:
177
- print(f" -> ✅ Rescue Mode found {len(scored_candidates)} candidates with Score > 0.")
178
 
179
- # 5. الترتيب واختيار النخبة
180
- # ترتيب تنازلي حسب النقاط
181
- scored_candidates.sort(key=lambda x: x['l1_score'], reverse=True)
182
-
183
- # ✅ طباعة النتائج للمراقبة
184
- if len(scored_candidates) > 0:
185
- print("-" * 75)
186
- print(f"{'SYMBOL':<12} | {'SCORE':<6} | {'TAGS'}")
187
- print("-" * 75)
188
- for item in scored_candidates[:5]: # طباعة أفضل 5 فقط لعدم إغراق السجل
189
- tags_str = ", ".join(item.get('tags', []))
190
- print(f"{item['symbol']:<12} | {item['l1_score']:<6.1f} | {tags_str}")
191
- print("-" * 75)
192
  else:
193
- print(f" [Layer 1] Still 0 candidates after Rescue Mode. Market is extremely weird or Data is bad.")
194
-
195
- # نختار أفضل 40 عملة فقط لتمريرها للمعالجة العميقة (L2/L3)
196
- final_selection = scored_candidates[:40]
197
 
198
- # تنظيف البيانات لتمرير ما يهم المعالج فقط
199
- cleaned_selection = []
200
- for item in final_selection:
201
- cleaned_selection.append({
202
- 'symbol': item['symbol'],
203
- 'quote_volume': item.get('quote_volume', 0),
204
- 'current_price': item.get('current_price', 0),
205
- 'type': ','.join(item.get('tags', [])), # تمرير التاغات كسترينغ
206
- 'l1_score': item.get('l1_score', 0)
207
- })
208
-
209
- print(f"✅ [Layer 1] Passed {len(cleaned_selection)} Synergy assets to Processor.")
210
- return cleaned_selection
211
 
212
- # ------------------------------------------------------------------
213
- # Stage 0: Universe Filter (Basic Liquidity Check)
214
- # ------------------------------------------------------------------
215
  async def _stage0_universe_filter(self) -> List[Dict[str, Any]]:
216
  """
217
- فلتر أولي سريع جداً لاستبعاد:
218
- - العملات ذات السيولة المعدومة (أقل من 300 ألف دولار).
219
- - العملات المستقرة والمحظورة.
220
- - العملات ذات السعر الصفري تقريباً (أخطاء بيانات).
221
  """
222
  try:
223
  tickers = await self.exchange.fetch_tickers()
224
  candidates = []
225
 
226
  for symbol, ticker in tickers.items():
227
- # نقبل فقط أزواج USDT
228
  if not symbol.endswith('/USDT'): continue
229
 
230
- # التحقق من القائمة السوداء
231
  base_curr = symbol.split('/')[0]
232
  if any(bad in base_curr for bad in self.BLACKLIST_TOKENS): continue
233
 
234
- # شرط السيولة (Quote Volume)
235
  quote_vol = ticker.get('quoteVolume')
236
  if not quote_vol or quote_vol < 300_000: continue # Min 300k USDT
237
 
238
- # شرط السعر المنطقي
239
  last_price = ticker.get('last')
240
  if not last_price or last_price < 0.0001: continue
241
 
@@ -243,41 +208,118 @@ class DataManager:
243
  'symbol': symbol,
244
  'quote_volume': quote_vol,
245
  'current_price': last_price,
246
- 'change_24h': ticker.get('percentage', 0.0)
247
  })
248
 
249
- # الترتيب حسب السيولة لضمان أننا نفحص الأقوى أولاً
250
- candidates.sort(key=lambda x: x['quote_volume'], reverse=True)
251
  return candidates
252
-
253
  except Exception as e:
254
  print(f"❌ [L1 Error] Universe filter failed: {e}")
255
  return []
256
 
257
- # ------------------------------------------------------------------
258
- # Data Fetching Helpers (Updated for Depth & Parallel Execution)
259
- # ------------------------------------------------------------------
260
- async def _fetch_technical_and_depth_batch(self, candidates: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  """
262
- جلب البيانات الفنية (OHLCV) + دفتر الطلبات (Order Book) بالتوازي.
263
- يتم تقسيم العملات إلى دفعات (Chunks) لتجنب الحظر.
264
  """
265
- chunk_size = 10 # عدد العملات في كل دفعة
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  results = []
267
 
268
  for i in range(0, len(candidates), chunk_size):
269
  chunk = candidates[i:i + chunk_size]
270
-
271
- # إنشاء مهام غير متزامنة لكل عملة في الدفعة
272
  chunk_tasks = [self._fetch_single_full_data(c) for c in chunk]
273
-
274
- # تنفيذ المهام وانتظار النتائج
275
  chunk_results = await asyncio.gather(*chunk_tasks)
276
-
277
- # تجميع النتائج الصالحة فقط
278
  results.extend([r for r in chunk_results if r is not None])
279
-
280
- # استراحة قصيرة جداً لتخفيف الحمل على الذاكرة والشبكة
281
  await asyncio.sleep(0.1)
282
 
283
  return results
@@ -286,159 +328,41 @@ class DataManager:
286
  """جلب بيانات عملة واحدة (شارت + عمق)"""
287
  symbol = candidate['symbol']
288
  try:
289
- # 1. طلب الشموع (OHLCV) - نحتاج 200 شمعة لحساب المتوسطات الطويلة (EMA 200)
290
- ohlcv_task = self.exchange.fetch_ohlcv(symbol, '1h', limit=205)
291
-
292
- # 2. طلب دفتر الطلبات (Order Book) - لقطة لأفضل 20 مستوى فقط
293
  ob_task = self.exchange.fetch_order_book(symbol, limit=20)
294
 
295
- # تنفيذ الطلبين معاً
296
  ohlcv_1h, order_book = await asyncio.gather(ohlcv_task, ob_task)
297
 
298
- # التحقق من جودة البيانات
299
- if not ohlcv_1h or len(ohlcv_1h) < 200:
300
- return None # بيانات غير كافية للتحليل
301
 
302
  candidate['ohlcv_1h'] = ohlcv_1h
303
  candidate['order_book_snapshot'] = order_book
304
  return candidate
305
 
306
  except Exception:
307
- # في حال فشل الجلب (Timeout أو غيره)، نتجاهل العملة
308
  return None
309
 
310
- # ------------------------------------------------------------------
311
- # 🧠 The Logic Core: Synergy Matrix Scoring (New V18.2 Logic)
312
- # ------------------------------------------------------------------
313
- def _calculate_synergy_score(self, data: Dict[str, Any]) -> Dict[str, Any]:
314
- """
315
- مصفوفة التوافق (Synergy Matrix):
316
- تقوم بحساب النقاط بناءً على العلاقات المترابطة بين المؤشرات.
317
- لا تعتمد على القيم المطلقة فقط، بل على سياق السوق.
318
- """
319
- try:
320
- # تحويل البيانات إلى DataFrame للتحليل
321
- df = pd.DataFrame(data['ohlcv_1h'], columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
322
- ob = data.get('order_book_snapshot', {})
323
-
324
- df['close'] = df['close'].astype(float)
325
- df['volume'] = df['volume'].astype(float)
326
-
327
- # --- 1. حساب المؤشرات الفنية الأساسية ---
328
-
329
- # EMA (العمود الفقري للاتجاه)
330
- df['ema50'] = df['close'].ewm(span=50, adjust=False).mean()
331
- df['ema200'] = df['close'].ewm(span=200, adjust=False).mean()
332
-
333
- # RSI (مؤشر الزخم)
334
- delta = df['close'].diff()
335
- gain = (delta.where(delta > 0, 0)).rolling(14).mean()
336
- loss = (-delta.where(delta < 0, 0)).rolling(14).mean()
337
- rs = gain / loss
338
- df['rsi'] = 100 - (100 / (1 + rs))
339
-
340
- # Volume MA (متوسط الحجم)
341
- df['vol_ma'] = df['volume'].rolling(20).mean()
342
-
343
- curr = df.iloc[-1]
344
- score = 0.0
345
- tags = []
346
-
347
- # --- 2. تحليل دفتر الطلبات (Macro Order Book) ---
348
- # حساب نسبة ضغط الشراء مقابل البيع (Bid/Ask Ratio)
349
- depth_ratio = 1.0
350
- total_bids = 0
351
- if ob and 'bids' in ob and 'asks' in ob:
352
- total_bids = sum([b[1] for b in ob['bids']])
353
- total_asks = sum([a[1] for a in ob['asks']])
354
- if total_asks > 0:
355
- depth_ratio = total_bids / total_asks
356
-
357
- # --- 3. مصفوفة العلاقات (Logic Execution) ---
358
-
359
- close = curr['close']
360
- ema50 = curr['ema50']
361
- ema200 = curr['ema200']
362
- rsi = curr['rsi']
363
-
364
- # الحالة A: الثور المؤكد (Confirmed Trend)
365
- # السعر فوق المتوسطات + يوجد دعم حقيقي في الدفتر
366
- if close > ema50 and close > ema200:
367
- base_trend = 20
368
- if depth_ratio > 1.1: # توافق إيجابي
369
- score += (base_trend * 1.5)
370
- tags.append("ConfirmedTrend")
371
- elif depth_ratio < 0.8: # تناقض (سعر صاعد لكن الحيتان يبيعون)
372
- score -= 20
373
- tags.append("BullTrap⚠️")
374
- else:
375
- score += base_trend # اتجاه صاعد عادي
376
-
377
- # الحالة B: صيد الحيتان / الارتداد (Whale Net)
378
- # السعر تحت المتوسطات (هابط) لكن الدفتر يظهر شراء عنيف
379
- elif close < ema50:
380
- if depth_ratio > 1.5 and rsi < 35: # شراء قوي جداً عند القاع
381
- score += 40 # نغفر سلبية المتوسط بسبب قوة الدفتر
382
- tags.append("WhaleNet🔥")
383
- elif depth_ratio > 1.2:
384
- score += 15
385
- tags.append("SupprortBuild")
386
- else:
387
- score -= 30 # هبوط بدون دعم = انهيار
388
-
389
- # الحالة C: الزخم والحجم (Momentum & Volume)
390
- # حساب الحجم النسبي (Relative Volume)
391
- rvol = curr['volume'] / curr['vol_ma'] if curr['vol_ma'] > 0 else 1.0
392
-
393
- if rsi > 50 and rsi < 75:
394
- if rvol > 1.2: # زخم سعري مع سيولة
395
- score += 15
396
- tags.append("MomVol")
397
- else:
398
- score += 5
399
-
400
- # حالة خاصة: الامتداد السعري المفرط (Over Extension)
401
- # السعر ابتعد كثيراً عن المتوسط (خطر التصحيح)
402
- dist_from_ema = (close - ema50) / ema50
403
- if dist_from_ema > 0.20: # 20% فوق المتوسط
404
- score -= 10
405
- tags.append("OverExtended")
406
-
407
- # إرجاع النتيجة
408
- return {
409
- 'is_eligible': score > SystemLimits.L1_MIN_AFFINITY_SCORE,
410
- 'score': score,
411
- 'tags': tags
412
- }
413
-
414
- except Exception as e:
415
- # في حال حدوث خطأ حسابي، نرفض العملة للأمان
416
- return {'is_eligible': False, 'score': 0, 'tags': []}
417
-
418
  # ==================================================================
419
- # 🎯 Public Helpers (Legacy & Utility Methods)
420
  # ==================================================================
421
  async def get_latest_price_async(self, symbol: str) -> float:
422
- """جلب آخر سعر للعملة"""
423
  try:
424
  ticker = await self.exchange.fetch_ticker(symbol)
425
  return float(ticker['last'])
426
  except Exception: return 0.0
427
 
428
  async def get_latest_ohlcv(self, symbol: str, timeframe: str = '5m', limit: int = 100) -> List[List[float]]:
429
- """جلب بيانات الشموع (OHLCV)"""
430
  try:
431
  candles = await self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
432
  return candles or []
433
  except Exception: return []
434
 
435
  async def get_order_book_snapshot(self, symbol: str, limit: int = 20) -> Dict[str, Any]:
436
- """جلب لقطة سريعة لدفتر الطلبات"""
437
  try:
438
  ob = await self.exchange.fetch_order_book(symbol, limit)
439
  return ob
440
  except Exception: return {}
441
 
442
  def get_supported_timeframes(self):
443
- """إرجاع الإطارات الزمنية المدعومة"""
444
  return list(self.exchange.timeframes.keys()) if self.exchange else []
 
1
  # ============================================================
2
  # 📂 ml_engine/data_manager.py
3
+ # (V36.0 - GEM-Architect: Regime-Adaptive Vision Full)
4
  # ============================================================
5
 
6
  import asyncio
 
9
  import logging
10
  import pandas as pd
11
  import numpy as np
12
+ import pandas_ta as ta
13
  from typing import List, Dict, Any
14
 
15
+ import ccxt.async_support as ccxt
16
+
17
+ # ✅ استيراد الدستور الديناميكي (لقراءة الحالة الحالية)
18
  try:
19
  from ml_engine.processor import SystemLimits
20
  except ImportError:
21
+ # Fallback إذا لم يتم التحميل بعد
22
  class SystemLimits:
23
+ L1_MIN_AFFINITY_SCORE = 15.0
24
+ CURRENT_REGIME = "RANGE"
25
 
26
+ # إعدادات التسجيل لإسكات الإزعاج
 
 
 
 
 
27
  logging.getLogger("httpx").setLevel(logging.WARNING)
28
  logging.getLogger("httpcore").setLevel(logging.WARNING)
29
  logging.getLogger("ccxt").setLevel(logging.WARNING)
30
 
31
  class DataManager:
32
  """
33
+ DataManager V36.0 (The Chameleon Eye)
34
+ - تتغير خوارزمية الفلترة (L1) تماماً بناءً على حالة السوق (Regime).
35
+ - تعتمد على SystemLimits.CURRENT_REGIME لتحديد التكتيك.
 
36
  """
37
 
38
  def __init__(self, contracts_db, whale_monitor, r2_service=None):
 
59
  'UP', 'DOWN', 'BEAR', 'BULL', '3S', '3L', 'USDD', 'USDP'
60
  ]
61
 
62
+ print(f"📦 [DataManager V36.0] Adaptive Vision Online.")
63
 
64
  async def initialize(self):
65
  """تهيئة مدير البيانات والاتصالات"""
66
  print(" > [DataManager] Starting initialization...")
67
  try:
 
68
  self.http_client = httpx.AsyncClient(timeout=60.0)
69
  await self._load_markets()
70
+ print(f"✅ [DataManager] Ready (Mode: {getattr(SystemLimits, 'CURRENT_REGIME', 'UNKNOWN')}).")
71
  except Exception as e:
72
  print(f"❌ [DataManager] Init Error: {e}")
73
  traceback.print_exc()
 
80
  if not self.exchange.markets:
81
  await self.exchange.load_markets()
82
  self.market_cache = self.exchange.markets
 
83
  except Exception as e:
84
  print(f"❌ [DataManager] Market load failed: {e}")
85
  traceback.print_exc()
 
93
  print("🛑 [DataManager] Connections Closed.")
94
 
95
  # ==================================================================
96
+ # 🚀 R2 Compatibility
97
  # ==================================================================
98
  async def load_contracts_from_r2(self):
 
99
  if not self.r2_service: return
100
  try:
101
  self.contracts_db = await self.r2_service.load_contracts_db_async()
 
105
  self.contracts_db = {}
106
 
107
  def get_contracts_db(self) -> Dict[str, Any]:
 
108
  return self.contracts_db
109
+
110
  # ==================================================================
111
+ # 🛡️ Layer 1: Regime-Adaptive Screening (The Core Update)
112
  # ==================================================================
113
  async def layer1_rapid_screening(self) -> List[Dict[str, Any]]:
114
  """
115
+ تقوم باختيار استراتيجية المسح بناءً على حالة السوق الحالية.
 
116
  """
117
+ # 1. تحديد الحالة من الدستور
118
+ # ملاحظة: AdaptiveHub هو المسؤول عن تحديث هذه القيمة في SystemLimits
119
+ current_regime = getattr(SystemLimits, "CURRENT_REGIME", "RANGE")
120
+ min_score = getattr(SystemLimits, "L1_MIN_AFFINITY_SCORE", 15.0)
121
 
122
+ print(f"🔍 [Layer 1] Screening Mode: {current_regime} | Min Score: {min_score}")
 
123
 
124
+ # 2. جلب الكون الأولي (Universe) - استخدام الفلتر الأساسي الكامل
125
+ all_tickers = await self._stage0_universe_filter()
126
+ if not all_tickers:
127
+ print("⚠️ [Layer 1] No tickers passed Stage 0.")
128
  return []
129
 
130
+ # 3. توجيه المسح حسب الاستراتيجية
131
+ candidates = []
 
132
 
133
+ if current_regime == "BULL":
134
+ candidates = self._pre_filter_bull(all_tickers)
135
+ elif current_regime == "BEAR":
136
+ candidates = self._pre_filter_bear(all_tickers)
137
+ elif current_regime == "DEAD":
138
+ candidates = self._pre_filter_dead(all_tickers)
139
+ else: # RANGE or Default
140
+ candidates = self._pre_filter_range(all_tickers)
141
+
142
+ print(f" -> Pre-filter selected {len(candidates)} candidates for Deep Scan.")
143
 
144
+ # 4. الجلب العميق للبيانات (Deep Data Fetch)
145
+ # نأخذ أفضل 60 عملة فقط لتوفير الموارد
146
+ top_candidates = candidates[:60]
147
+ enriched_data = await self._fetch_technical_and_depth_batch(top_candidates)
148
 
149
+ if not enriched_data:
150
+ print("❌ [Layer 1] Failed to fetch deep data.")
151
+ return []
152
 
153
+ # 5. حساب النقاط (Synergy Score) بناءً على الحالة
154
+ final_selection = []
 
155
  for item in enriched_data:
156
+ synergy = self._calculate_adaptive_score(item, current_regime)
157
+ item['l1_score'] = synergy['score']
158
+ item['tags'] = synergy['tags']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
+ if item['l1_score'] >= min_score:
161
+ final_selection.append(item)
162
 
163
+ # ترتيب نهائي وتنظيف
164
+ final_selection.sort(key=lambda x: x['l1_score'], reverse=True)
165
+
166
+ # طباعة عينة للمراقبة
167
+ if final_selection:
168
+ print(f" -> Top pick: {final_selection[0]['symbol']} (Score: {final_selection[0]['l1_score']:.1f})")
 
 
 
 
 
 
 
169
  else:
170
+ print("⚠️ [Layer 1] No candidates passed the adaptive score threshold.")
 
 
 
171
 
172
+ # نمرر أفضل 40 للمعالج
173
+ return [
174
+ {
175
+ 'symbol': c['symbol'],
176
+ 'quote_volume': c.get('quote_volume', 0),
177
+ 'current_price': c.get('current_price', 0),
178
+ 'type': ','.join(c.get('tags', [])),
179
+ 'l1_score': c.get('l1_score', 0)
180
+ }
181
+ for c in final_selection[:40]
182
+ ]
 
 
183
 
184
+ # ==================================================================
185
+ # 🌍 Stage 0 & Pre-Filters
186
+ # ==================================================================
187
  async def _stage0_universe_filter(self) -> List[Dict[str, Any]]:
188
  """
189
+ فلتر أولي سريع لاستبعاد العملات الميتة أو المحظورة.
 
 
 
190
  """
191
  try:
192
  tickers = await self.exchange.fetch_tickers()
193
  candidates = []
194
 
195
  for symbol, ticker in tickers.items():
 
196
  if not symbol.endswith('/USDT'): continue
197
 
 
198
  base_curr = symbol.split('/')[0]
199
  if any(bad in base_curr for bad in self.BLACKLIST_TOKENS): continue
200
 
 
201
  quote_vol = ticker.get('quoteVolume')
202
  if not quote_vol or quote_vol < 300_000: continue # Min 300k USDT
203
 
 
204
  last_price = ticker.get('last')
205
  if not last_price or last_price < 0.0001: continue
206
 
 
208
  'symbol': symbol,
209
  'quote_volume': quote_vol,
210
  'current_price': last_price,
211
+ 'change_24h': float(ticker.get('percentage', 0.0))
212
  })
213
 
 
 
214
  return candidates
 
215
  except Exception as e:
216
  print(f"❌ [L1 Error] Universe filter failed: {e}")
217
  return []
218
 
219
+ def _pre_filter_bull(self, tickers):
220
+ """🐂 Bull Mode: السيولة العالية والارتفاع في السعر"""
221
+ filtered = [t for t in tickers if t['change_24h'] > -2.0 and t['quote_volume'] > 500_000]
222
+ filtered.sort(key=lambda x: (x['change_24h'], x['quote_volume']), reverse=True)
223
+ return filtered
224
+
225
+ def _pre_filter_bear(self, tickers):
226
+ """🐻 Bear Mode: الارتدادات من القاع (Panic Selling)"""
227
+ filtered = [t for t in tickers if t['change_24h'] < -5.0 and t['quote_volume'] > 1_000_000]
228
+ filtered.sort(key=lambda x: x['change_24h'], reverse=False) # تصاعدي (الأكثر سلبية)
229
+ return filtered
230
+
231
+ def _pre_filter_range(self, tickers):
232
+ """↔️ Range Mode: الاستقرار والسيولة المتوسطة"""
233
+ filtered = [t for t in tickers if -5.0 < t['change_24h'] < 5.0 and t['quote_volume'] > 300_000]
234
+ filtered.sort(key=lambda x: x['quote_volume'], reverse=True)
235
+ return filtered
236
+
237
+ def _pre_filter_dead(self, tickers):
238
+ """💤 Dead/Accumulation Mode: سيولة منخفضة وتحرك مفاجئ"""
239
+ filtered = [t for t in tickers if t['quote_volume'] > 100_000]
240
+ import random
241
+ random.shuffle(filtered) # عشوائية لاستكشاف الجواهر
242
+ return filtered
243
+
244
+ # ==================================================================
245
+ # 🧠 Adaptive Scoring Matrix
246
+ # ==================================================================
247
+ def _calculate_adaptive_score(self, item: Dict[str, Any], regime: str) -> Dict[str, Any]:
248
  """
249
+ حساب النقاط بناءً على السياق (Regime-Specific Scoring).
 
250
  """
251
+ try:
252
+ df = pd.DataFrame(item['ohlcv_1h'], columns=['ts', 'o', 'h', 'l', 'c', 'v'])
253
+ df['c'] = df['c'].astype(float)
254
+
255
+ # المؤشرات الأساسية
256
+ curr_close = df['c'].iloc[-1]
257
+ rsi = ta.rsi(df['c'], 14).iloc[-1]
258
+ ema50 = ta.ema(df['c'], 50).iloc[-1]
259
+
260
+ score = 0.0
261
+ tags = []
262
+
263
+ # 🐂 BULL LOGIC
264
+ if regime == "BULL":
265
+ # نحب الـ RSI العالي (زخم) لكن ليس المفرط جداً
266
+ if 55 < rsi < 80: score += 20
267
+ if curr_close > ema50: score += 20
268
+ tags.append("TrendFollow")
269
+
270
+ # 🐻 BEAR LOGIC
271
+ elif regime == "BEAR":
272
+ # نحب الـ RSI المنخفض (تشبع بيعي)
273
+ if rsi < 30:
274
+ score += 30
275
+ tags.append("Oversold")
276
+ elif rsi > 60:
277
+ score -= 20 # خطر
278
+
279
+ # في السوق الهابط، السعر غالباً تحت EMA، نبحث عن الابتعاد الشديد عنه
280
+ dist = (curr_close - ema50) / ema50
281
+ if dist < -0.15:
282
+ score += 15
283
+ tags.append("DeepValue")
284
+
285
+ # ↔️ RANGE LOGIC
286
+ elif regime == "RANGE":
287
+ # نحب الـ RSI في المنتصف للارتداد
288
+ if rsi < 35:
289
+ score += 25; tags.append("RangeBot")
290
+ elif rsi > 65:
291
+ score -= 10; tags.append("RangeTop")
292
+ else:
293
+ score += 10 # منطقة آمنة
294
+
295
+ # دفتر الطلبات (مشترك لكن بأوزان مختلفة ضمنياً)
296
+ ob = item.get('order_book_snapshot', {})
297
+ if ob:
298
+ bids = sum([float(x[1]) for x in ob.get('bids', [])[:10]])
299
+ asks = sum([float(x[1]) for x in ob.get('asks', [])[:10]])
300
+ if asks > 0:
301
+ ratio = bids / asks
302
+ if ratio > 1.5: score += 15; tags.append("BidWall")
303
+ if regime == "BEAR" and ratio > 2.0: score += 20 # جدار الشراء في الهبوط إشارة قوية
304
+
305
+ return {'score': score, 'tags': tags}
306
+
307
+ except Exception:
308
+ return {'score': 0, 'tags': ['Error']}
309
+
310
+ # ==================================================================
311
+ # ⚡ Batch Fetching Utilities (Parallel Execution)
312
+ # ==================================================================
313
+ async def _fetch_technical_and_depth_batch(self, candidates: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
314
+ """جلب البيانات الفنية + دفتر الطلبات بالتوازي"""
315
+ chunk_size = 10
316
  results = []
317
 
318
  for i in range(0, len(candidates), chunk_size):
319
  chunk = candidates[i:i + chunk_size]
 
 
320
  chunk_tasks = [self._fetch_single_full_data(c) for c in chunk]
 
 
321
  chunk_results = await asyncio.gather(*chunk_tasks)
 
 
322
  results.extend([r for r in chunk_results if r is not None])
 
 
323
  await asyncio.sleep(0.1)
324
 
325
  return results
 
328
  """جلب بيانات عملة واحدة (شارت + عمق)"""
329
  symbol = candidate['symbol']
330
  try:
331
+ ohlcv_task = self.exchange.fetch_ohlcv(symbol, '1h', limit=100)
 
 
 
332
  ob_task = self.exchange.fetch_order_book(symbol, limit=20)
333
 
 
334
  ohlcv_1h, order_book = await asyncio.gather(ohlcv_task, ob_task)
335
 
336
+ if not ohlcv_1h or len(ohlcv_1h) < 50:
337
+ return None
 
338
 
339
  candidate['ohlcv_1h'] = ohlcv_1h
340
  candidate['order_book_snapshot'] = order_book
341
  return candidate
342
 
343
  except Exception:
 
344
  return None
345
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
  # ==================================================================
347
+ # 🎯 Public Helpers
348
  # ==================================================================
349
  async def get_latest_price_async(self, symbol: str) -> float:
 
350
  try:
351
  ticker = await self.exchange.fetch_ticker(symbol)
352
  return float(ticker['last'])
353
  except Exception: return 0.0
354
 
355
  async def get_latest_ohlcv(self, symbol: str, timeframe: str = '5m', limit: int = 100) -> List[List[float]]:
 
356
  try:
357
  candles = await self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
358
  return candles or []
359
  except Exception: return []
360
 
361
  async def get_order_book_snapshot(self, symbol: str, limit: int = 20) -> Dict[str, Any]:
 
362
  try:
363
  ob = await self.exchange.fetch_order_book(symbol, limit)
364
  return ob
365
  except Exception: return {}
366
 
367
  def get_supported_timeframes(self):
 
368
  return list(self.exchange.timeframes.keys()) if self.exchange else []