Riy777 commited on
Commit
49fec9c
·
verified ·
1 Parent(s): 8083847

Update ml_engine/data_manager.py

Browse files
Files changed (1) hide show
  1. ml_engine/data_manager.py +44 -112
ml_engine/data_manager.py CHANGED
@@ -1,6 +1,6 @@
1
  # ============================================================
2
  # 📂 ml_engine/data_manager.py
3
- # (V62.0 - GEM-Architect: Accumulation Depth & Vitality Check)
4
  # ============================================================
5
 
6
  import asyncio
@@ -38,13 +38,13 @@ class DataManager:
38
  self.http_client = None
39
  self.market_cache = {}
40
 
41
- # القائمة السوداء (نحتفظ بها كما هي لتجنب العملات القمامة فعلياً)
42
  self.BLACKLIST_TOKENS = [
43
  'USDT', 'USDC', 'DAI', 'TUSD', 'BUSD', 'FDUSD', 'EUR', 'PAX',
44
  'UP', 'DOWN', 'BEAR', 'BULL', '3S', '3L'
45
  ]
46
 
47
- print(f"📦 [DataManager V62.0] Accumulation Depth & Vitality Check Active.")
48
 
49
  async def initialize(self):
50
  print(" > [DataManager] Starting initialization...")
@@ -77,15 +77,15 @@ class DataManager:
77
  # ==================================================================
78
  async def layer1_rapid_screening(self, adaptive_hub_ref=None) -> List[Dict[str, Any]]:
79
  self.adaptive_hub_ref = adaptive_hub_ref
80
- print(f"🔍 [Layer 1] Screening for Safe Bottoms, Accumulation & Momentum...")
81
 
82
- # 1. فلتر السيولة الأساسي
83
  initial_candidates = await self._stage0_universe_filter()
84
  if not initial_candidates:
85
  print("⚠️ [Layer 1] Stage 0 returned 0 candidates.")
86
  return []
87
 
88
- # 2. جلب البيانات الفنية (دفعة أكبر لزيادة الفرص)
89
  top_candidates = initial_candidates[:600]
90
  enriched_data = await self._fetch_technical_data_batch(top_candidates)
91
 
@@ -96,81 +96,65 @@ class DataManager:
96
  classification = self._classify_opportunity_type(item)
97
 
98
  if classification['type'] != 'NONE':
99
- # تشخيص حالة السوق
100
  regime_info = self._diagnose_asset_regime(item)
101
  item['asset_regime'] = regime_info['regime']
102
  item['asset_regime_conf'] = regime_info['conf']
103
 
104
- # تخزين نوع الاستراتيجية والنتيجة الأولية
105
  item['strategy_type'] = classification['type']
106
  item['l1_sort_score'] = classification['score']
107
  item['strategy_tag'] = classification['type']
108
 
109
- # فحص الـ Regime (نرفض الانفجارات في الأسواق الميتة، لكن نقبل التجميع)
110
  if regime_info['regime'] == 'DEAD' and classification['type'] == 'MOMENTUM_LAUNCH':
111
- # استثناء: إذا كان "Squeeze" (تجميع)، نسمح بمروره حتى لو السوق ميت
112
  if not classification.get('is_squeeze', False):
113
  continue
114
 
115
  semi_final_list.append(item)
116
 
117
- # 4. 🧱 فحص عمق السوق (Order Book Check) للمرشحين للتجميع
118
- # هذا هو "مصدر الكشف" الجديد الذي طلبته
119
  final_list = []
120
- print(f" 🛡️ [Layer 1.5] Checking Depth Support for {len(semi_final_list)} candidates...")
121
-
122
- # نأخذ أفضل 50 مرشحاً فقط لفحص دفتر الطلبات لتجنب حظر API
123
  semi_final_list.sort(key=lambda x: x['l1_sort_score'], reverse=True)
124
- candidates_for_depth = semi_final_list[:150]
 
 
 
125
 
126
  for item in candidates_for_depth:
127
- # إذا كانت تجميع (Squeeze) أو قاع، نفحص هل هناك دعم حقيقي في الأسفل؟
128
  if item['strategy_type'] in ['ACCUMULATION_SQUEEZE', 'SAFE_BOTTOM']:
129
  try:
130
- # حساب نطاق الساعتين القادمتين (2 * ATR)
131
  atr_val = item.get('atr_value', 0.0)
132
  curr_price = item.get('current_price', 0.0)
133
 
134
  if atr_val > 0 and curr_price > 0:
135
  range_2h = atr_val * 2.0
136
- # فحص ضغط دفتر الطلبات في هذا النطاق
137
  ob_score = await self._check_ob_pressure(item['symbol'], curr_price, range_2h)
138
 
139
- # تعزيز النتيجة بناءً على دعم دفتر الطلبات
140
- if ob_score > 0.6: # دعم قوي
141
- item['l1_sort_score'] += 0.15 # بونص
142
  item['note'] = f"Strong Depth Support ({ob_score:.2f})"
143
- elif ob_score < 0.4: # دعم ضعيف
144
- item['l1_sort_score'] -= 0.10 # عقوبة
145
  except Exception: pass
146
 
147
- # حقن الإعدادات الديناميكية
148
  if self.adaptive_hub_ref:
149
  dynamic_config = self.adaptive_hub_ref.get_regime_config(item['asset_regime'])
150
  item['dynamic_limits'] = dynamic_config
151
 
152
  final_list.append(item)
153
 
154
- # 5. الترتيب النهائي والإرجاع
155
  final_list.sort(key=lambda x: x['l1_sort_score'], reverse=True)
156
- selection = final_list[:150]
157
 
158
- print(f"✅ [Layer 1] Passed {len(selection)} candidates (With Depth Verification).")
159
  return selection
160
 
161
  # ==================================================================
162
- # 🧱 Order Book Depth Scanner (2-Hour Range Logic)
163
  # ==================================================================
164
  async def _check_ob_pressure(self, symbol: str, current_price: float, price_range: float) -> float:
165
- """
166
- يفحص دفتر الطلبات داخل النطاق السعري المتوقع للساعتين القادمتين.
167
- النطاق = السعر الحالي +/- (ATR_1H * 2)
168
- يعيد نسبة قوة المشترين (0.0 إلى 1.0).
169
- """
170
  try:
171
- # جلب لقطة سريعة لدفتر الطلبات (50 مستوى كافية للتجميع القريب)
172
  ob = await self.exchange.fetch_order_book(symbol, limit=50)
173
-
174
  bids = ob['bids']
175
  asks = ob['asks']
176
 
@@ -180,43 +164,29 @@ class DataManager:
180
  support_vol = 0.0
181
  resistance_vol = 0.0
182
 
183
- # تجميع طلبات الشراء داخل نطاق الدعم المتوقع
184
  for p, v in bids:
185
- if p >= min_price:
186
- support_vol += v
187
- else: break # بما أن Bids مرتبة تنازلياً
188
 
189
- # تجميع طلبات البيع داخل نطاق المقاومة المتوقع
190
  for p, v in asks:
191
- if p <= max_price:
192
- resistance_vol += v
193
- else: break # بما أن Asks مرتبة تصاعدياً
194
 
195
  if (support_vol + resistance_vol) == 0: return 0.5
196
-
197
- # نسبة ضغط الشراء (Imbalance)
198
- pressure_ratio = support_vol / (support_vol + resistance_vol)
199
- return pressure_ratio
200
-
201
  except Exception:
202
- return 0.5 # حياد في حال الفشل
203
 
204
  # ==================================================================
205
- # ⚖️ The Dual-Classifier Logic (GEM-Architect: ACCUMULATION UPGRADE)
206
  # ==================================================================
207
  def _classify_opportunity_type(self, data: Dict[str, Any]) -> Dict[str, Any]:
208
- """
209
- تم التعديل ليشمل منطق التجميع (RSI 45-60) وفلتر الحياة (ATR).
210
- """
211
  try:
212
  df_1h = self._calc_indicators(data['ohlcv_1h_raw'])
213
  curr = df_1h.iloc[-1]
214
-
215
- # تخزين ATR لاستخدامه لاحقاً في فحص دفتر الطلبات
216
  data['atr_value'] = curr['atr']
217
  except: return {'type': 'NONE', 'score': 0}
218
 
219
- # --- المؤشرات الأساسية ---
220
  rsi = curr['rsi']
221
  close = curr['close']
222
  ema20 = curr['ema20']
@@ -224,40 +194,29 @@ class DataManager:
224
  ema200 = curr['ema200'] if 'ema200' in curr else ema50
225
  atr = curr['atr']
226
 
227
- # Bollinger Bands
228
  lower_bb = curr['lower_bb'] if 'lower_bb' in curr else (curr['ema20'] - (2*curr['atr']))
229
  upper_bb = curr['upper_bb'] if 'upper_bb' in curr else (curr['ema20'] + (2*curr['atr']))
230
  bb_width = (upper_bb - lower_bb) / curr['ema20'] if curr['ema20'] > 0 else 1.0
231
 
232
- # 🔥 1. Dead Coin Filter (فلتر العملات الميتة)
233
- # نحسب نسبة الـ ATR للسعر. إذا كانت الحركة أقل من 0.4% في الساعة، نرفضها.
234
  volatility_pct = (atr / close) * 100 if close > 0 else 0
235
- if volatility_pct < 0.4:
236
- return {'type': 'NONE', 'score': 0}
237
 
238
- # 🛡️ TYPE 1: SAFE_BOTTOM (RSI < 45)
239
  if rsi < 45:
240
  dist_from_ema = (ema50 - close) / ema50
241
- # السماح بمسافة أكبر قليلاً عن الخط السفلي (5%)
242
  if close <= lower_bb * 1.05 and dist_from_ema > 0.015:
243
  score = (55 - rsi) / 20.0
244
  return {'type': 'SAFE_BOTTOM', 'score': min(score, 1.0)}
245
 
246
- # 🔋 TYPE 2: ACCUMULATION_SQUEEZE (The Missing Link)
247
- # المنطق: RSI محايد (45-60) + قناة ضيقة (Squeeze) + دعم دفتر الطلبات (سيفحص لاحقاً)
248
  elif 45 <= rsi <= 60:
249
- if bb_width < 0.12: # وسعنا النطاق قليلاً لـ 12% لزيادة المرونة
250
- # نفضل أن يكون السعر يحاول الصعود فوق متوسط 20
251
- if close > ema20 * 0.995: # السماح بكسر وهمي بسيط
252
  score = 1.0 - (bb_width * 4.0)
253
- # نستخدم وسم خاص لتمييزها
254
- return {
255
- 'type': 'ACCUMULATION_SQUEEZE',
256
- 'score': max(score, 0.5),
257
- 'is_squeeze': True
258
- }
259
 
260
- # 🚀 TYPE 3: MOMENTUM_LAUNCH (Classic)
261
  elif 60 < rsi < 80:
262
  if close > ema50 and close > ema200:
263
  dist_to_upper = (upper_bb - close) / close
@@ -268,16 +227,18 @@ class DataManager:
268
  return {'type': 'NONE', 'score': 0}
269
 
270
  # ==================================================================
271
- # 🔍 Stage 0: Universe Filter (RELAXED USD CALC)
272
  # ==================================================================
273
  async def _stage0_universe_filter(self) -> List[Dict[str, Any]]:
274
  try:
275
- print(" 🛡️ [Stage 0] Fetching Tickers (Accumulation Mode)...")
 
 
 
276
  tickers = await self.exchange.fetch_tickers()
277
  candidates = []
278
 
279
  SOVEREIGN_COINS = ['BTC/USDT', 'ETH/USDT', 'SOL/USDT', 'BNB/USDT', 'XRP/USDT']
280
-
281
  reject_stats = {"volume": 0, "change": 0, "blacklist": 0}
282
 
283
  for symbol, ticker in tickers.items():
@@ -294,9 +255,9 @@ class DataManager:
294
 
295
  is_sovereign = symbol in SOVEREIGN_COINS
296
 
297
- # تخفيف فلتر السيولة للسماح بعملات التجميع الصغيرة
298
  if not is_sovereign:
299
- if calc_quote_vol < 150000:
300
  reject_stats["volume"] += 1
301
  continue
302
 
@@ -315,6 +276,7 @@ class DataManager:
315
  })
316
 
317
  candidates.sort(key=lambda x: x['quote_volume'], reverse=True)
 
318
  return candidates
319
 
320
  except Exception as e:
@@ -322,36 +284,6 @@ class DataManager:
322
  traceback.print_exc()
323
  return []
324
 
325
- # ------------------------------------------------------------------
326
- # 🧭 The Diagnoser
327
- # ------------------------------------------------------------------
328
- def _diagnose_asset_regime(self, item: Dict[str, Any]) -> Dict[str, Any]:
329
- try:
330
- if 'df_1h' not in item: return {'regime': 'RANGE', 'conf': 0.0}
331
- df = item['df_1h']
332
- curr = df.iloc[-1]
333
- price = curr['close']
334
- ema20 = curr['ema20']
335
- ema50 = curr['ema50']
336
- rsi = curr['rsi']
337
- atr = curr['atr']
338
- atr_pct = (atr / price) * 100 if price > 0 else 0
339
-
340
- regime = "RANGE"
341
- conf = 0.5
342
-
343
- if atr_pct < 0.4: return {'regime': 'DEAD', 'conf': 0.9}
344
-
345
- if price > ema20 and ema20 > ema50 and rsi > 50:
346
- regime = "BULL"
347
- conf = 0.8 if rsi > 55 else 0.6
348
- elif price < ema20 and ema20 < ema50 and rsi < 50:
349
- regime = "BEAR"
350
- conf = 0.8 if rsi < 45 else 0.6
351
-
352
- return {'regime': regime, 'conf': conf}
353
- except Exception: return {'regime': 'RANGE', 'conf': 0.0}
354
-
355
  # ------------------------------------------------------------------
356
  # Helpers & Indicators
357
  # ------------------------------------------------------------------
@@ -393,7 +325,7 @@ class DataManager:
393
  tr = np.maximum(df['h']-df['l'], np.maximum(abs(df['h']-df['c'].shift()), abs(df['l']-df['c'].shift())))
394
  df['atr'] = tr.rolling(14).mean()
395
 
396
- # Bollinger Bands (Standard: 20, 2)
397
  std = df['c'].rolling(20).std()
398
  df['upper_bb'] = df['ema20'] + (2 * std)
399
  df['lower_bb'] = df['ema20'] - (2 * std)
 
1
  # ============================================================
2
  # 📂 ml_engine/data_manager.py
3
+ # (V63.0 - GEM-Architect: High Volume Filter & Depth Scan)
4
  # ============================================================
5
 
6
  import asyncio
 
38
  self.http_client = None
39
  self.market_cache = {}
40
 
41
+ # القائمة السوداء
42
  self.BLACKLIST_TOKENS = [
43
  'USDT', 'USDC', 'DAI', 'TUSD', 'BUSD', 'FDUSD', 'EUR', 'PAX',
44
  'UP', 'DOWN', 'BEAR', 'BULL', '3S', '3L'
45
  ]
46
 
47
+ print(f"📦 [DataManager V63.0] High Liquidity (1M+) & Depth Scan Active.")
48
 
49
  async def initialize(self):
50
  print(" > [DataManager] Starting initialization...")
 
77
  # ==================================================================
78
  async def layer1_rapid_screening(self, adaptive_hub_ref=None) -> List[Dict[str, Any]]:
79
  self.adaptive_hub_ref = adaptive_hub_ref
80
+ print(f"🔍 [Layer 1] Screening for High Vol Assets (Bottom/Acc/Mom)...")
81
 
82
+ # 1. فلتر السيولة الأساسي (الآن صارم: 1 مليون دولار)
83
  initial_candidates = await self._stage0_universe_filter()
84
  if not initial_candidates:
85
  print("⚠️ [Layer 1] Stage 0 returned 0 candidates.")
86
  return []
87
 
88
+ # 2. جلب البيانات الفنية
89
  top_candidates = initial_candidates[:600]
90
  enriched_data = await self._fetch_technical_data_batch(top_candidates)
91
 
 
96
  classification = self._classify_opportunity_type(item)
97
 
98
  if classification['type'] != 'NONE':
 
99
  regime_info = self._diagnose_asset_regime(item)
100
  item['asset_regime'] = regime_info['regime']
101
  item['asset_regime_conf'] = regime_info['conf']
102
 
 
103
  item['strategy_type'] = classification['type']
104
  item['l1_sort_score'] = classification['score']
105
  item['strategy_tag'] = classification['type']
106
 
107
+ # فحص الـ Regime
108
  if regime_info['regime'] == 'DEAD' and classification['type'] == 'MOMENTUM_LAUNCH':
 
109
  if not classification.get('is_squeeze', False):
110
  continue
111
 
112
  semi_final_list.append(item)
113
 
114
+ # 4. 🧱 فحص عمق السوق (Order Book Check)
 
115
  final_list = []
116
+ # نأخذ أفضل 50 مرشحاً لفحص دفتر الطلبات
 
 
117
  semi_final_list.sort(key=lambda x: x['l1_sort_score'], reverse=True)
118
+ candidates_for_depth = semi_final_list[:50]
119
+
120
+ if candidates_for_depth:
121
+ print(f" 🛡️ [Layer 1.5] Checking Depth Support for {len(candidates_for_depth)} candidates...")
122
 
123
  for item in candidates_for_depth:
 
124
  if item['strategy_type'] in ['ACCUMULATION_SQUEEZE', 'SAFE_BOTTOM']:
125
  try:
 
126
  atr_val = item.get('atr_value', 0.0)
127
  curr_price = item.get('current_price', 0.0)
128
 
129
  if atr_val > 0 and curr_price > 0:
130
  range_2h = atr_val * 2.0
 
131
  ob_score = await self._check_ob_pressure(item['symbol'], curr_price, range_2h)
132
 
133
+ if ob_score > 0.6:
134
+ item['l1_sort_score'] += 0.15
 
135
  item['note'] = f"Strong Depth Support ({ob_score:.2f})"
136
+ elif ob_score < 0.4:
137
+ item['l1_sort_score'] -= 0.10
138
  except Exception: pass
139
 
 
140
  if self.adaptive_hub_ref:
141
  dynamic_config = self.adaptive_hub_ref.get_regime_config(item['asset_regime'])
142
  item['dynamic_limits'] = dynamic_config
143
 
144
  final_list.append(item)
145
 
 
146
  final_list.sort(key=lambda x: x['l1_sort_score'], reverse=True)
147
+ selection = final_list[:60]
148
 
149
+ print(f"✅ [Layer 1] Passed {len(selection)} active candidates.")
150
  return selection
151
 
152
  # ==================================================================
153
+ # 🧱 Order Book Depth Scanner
154
  # ==================================================================
155
  async def _check_ob_pressure(self, symbol: str, current_price: float, price_range: float) -> float:
 
 
 
 
 
156
  try:
 
157
  ob = await self.exchange.fetch_order_book(symbol, limit=50)
 
158
  bids = ob['bids']
159
  asks = ob['asks']
160
 
 
164
  support_vol = 0.0
165
  resistance_vol = 0.0
166
 
 
167
  for p, v in bids:
168
+ if p >= min_price: support_vol += v
169
+ else: break
 
170
 
 
171
  for p, v in asks:
172
+ if p <= max_price: resistance_vol += v
173
+ else: break
 
174
 
175
  if (support_vol + resistance_vol) == 0: return 0.5
176
+ return support_vol / (support_vol + resistance_vol)
 
 
 
 
177
  except Exception:
178
+ return 0.5
179
 
180
  # ==================================================================
181
+ # ⚖️ The Dual-Classifier Logic
182
  # ==================================================================
183
  def _classify_opportunity_type(self, data: Dict[str, Any]) -> Dict[str, Any]:
 
 
 
184
  try:
185
  df_1h = self._calc_indicators(data['ohlcv_1h_raw'])
186
  curr = df_1h.iloc[-1]
 
 
187
  data['atr_value'] = curr['atr']
188
  except: return {'type': 'NONE', 'score': 0}
189
 
 
190
  rsi = curr['rsi']
191
  close = curr['close']
192
  ema20 = curr['ema20']
 
194
  ema200 = curr['ema200'] if 'ema200' in curr else ema50
195
  atr = curr['atr']
196
 
 
197
  lower_bb = curr['lower_bb'] if 'lower_bb' in curr else (curr['ema20'] - (2*curr['atr']))
198
  upper_bb = curr['upper_bb'] if 'upper_bb' in curr else (curr['ema20'] + (2*curr['atr']))
199
  bb_width = (upper_bb - lower_bb) / curr['ema20'] if curr['ema20'] > 0 else 1.0
200
 
201
+ # 🔥 1. Dead Coin Filter (فلتر النبض)
 
202
  volatility_pct = (atr / close) * 100 if close > 0 else 0
203
+ if volatility_pct < 0.4: return {'type': 'NONE', 'score': 0}
 
204
 
205
+ # 🛡️ TYPE 1: SAFE_BOTTOM
206
  if rsi < 45:
207
  dist_from_ema = (ema50 - close) / ema50
 
208
  if close <= lower_bb * 1.05 and dist_from_ema > 0.015:
209
  score = (55 - rsi) / 20.0
210
  return {'type': 'SAFE_BOTTOM', 'score': min(score, 1.0)}
211
 
212
+ # 🔋 TYPE 2: ACCUMULATION_SQUEEZE
 
213
  elif 45 <= rsi <= 60:
214
+ if bb_width < 0.12:
215
+ if close > ema20 * 0.995:
 
216
  score = 1.0 - (bb_width * 4.0)
217
+ return {'type': 'ACCUMULATION_SQUEEZE', 'score': max(score, 0.5), 'is_squeeze': True}
 
 
 
 
 
218
 
219
+ # 🚀 TYPE 3: MOMENTUM_LAUNCH
220
  elif 60 < rsi < 80:
221
  if close > ema50 and close > ema200:
222
  dist_to_upper = (upper_bb - close) / close
 
227
  return {'type': 'NONE', 'score': 0}
228
 
229
  # ==================================================================
230
+ # 🔍 Stage 0: Universe Filter (STRICT 1M FILTER)
231
  # ==================================================================
232
  async def _stage0_universe_filter(self) -> List[Dict[str, Any]]:
233
  try:
234
+ # 🔥 إعداد الحد الأدنى لحجم التداول (يمكنك تغييره إلى 5 مليون من هنا)
235
+ MIN_VOLUME_THRESHOLD = 1000000.0 # 1 Million USDT
236
+
237
+ print(f" 🛡️ [Stage 0] Fetching Tickers (Min Vol: ${MIN_VOLUME_THRESHOLD:,.0f})...")
238
  tickers = await self.exchange.fetch_tickers()
239
  candidates = []
240
 
241
  SOVEREIGN_COINS = ['BTC/USDT', 'ETH/USDT', 'SOL/USDT', 'BNB/USDT', 'XRP/USDT']
 
242
  reject_stats = {"volume": 0, "change": 0, "blacklist": 0}
243
 
244
  for symbol, ticker in tickers.items():
 
255
 
256
  is_sovereign = symbol in SOVEREIGN_COINS
257
 
258
+ # 🔥 الفلتر الصارم الجديد: رفض أي عملة تحت المليون
259
  if not is_sovereign:
260
+ if calc_quote_vol < MIN_VOLUME_THRESHOLD:
261
  reject_stats["volume"] += 1
262
  continue
263
 
 
276
  })
277
 
278
  candidates.sort(key=lambda x: x['quote_volume'], reverse=True)
279
+ print(f" ℹ️ [Stage 0] Ignored {reject_stats['volume']} low-vol coins.")
280
  return candidates
281
 
282
  except Exception as e:
 
284
  traceback.print_exc()
285
  return []
286
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  # ------------------------------------------------------------------
288
  # Helpers & Indicators
289
  # ------------------------------------------------------------------
 
325
  tr = np.maximum(df['h']-df['l'], np.maximum(abs(df['h']-df['c'].shift()), abs(df['l']-df['c'].shift())))
326
  df['atr'] = tr.rolling(14).mean()
327
 
328
+ # Bollinger Bands
329
  std = df['c'].rolling(20).std()
330
  df['upper_bb'] = df['ema20'] + (2 * std)
331
  df['lower_bb'] = df['ema20'] - (2 * std)