Riy777 commited on
Commit
8c2f875
·
verified ·
1 Parent(s): 1b9d614

Update ml_engine/data_manager.py

Browse files
Files changed (1) hide show
  1. ml_engine/data_manager.py +149 -60
ml_engine/data_manager.py CHANGED
@@ -1,6 +1,10 @@
 
 
 
 
1
  # ============================================================
2
  # 📂 ml_engine/data_manager.py
3
- # (V61.1 - GEM-Architect: Relaxed Filtering & Dual Classification)
4
  # ============================================================
5
 
6
  import asyncio
@@ -10,7 +14,7 @@ import ccxt.async_support as ccxt
10
  import logging
11
  import pandas as pd
12
  import numpy as np
13
- from typing import List, Dict, Any
14
 
15
  # محاولة استيراد حدود النظام
16
  try:
@@ -44,7 +48,7 @@ class DataManager:
44
  'UP', 'DOWN', 'BEAR', 'BULL', '3S', '3L'
45
  ]
46
 
47
- print(f"📦 [DataManager V61.1] Relaxed Filtering Active.")
48
 
49
  async def initialize(self):
50
  print(" > [DataManager] Starting initialization...")
@@ -73,108 +77,195 @@ class DataManager:
73
  return self.contracts_db
74
 
75
  # ==================================================================
76
- # 🧠 Layer 1: Classification (Bottom vs Momentum)
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 & MOMENTUM LAUNCHES...")
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
- # نأخذ أكبر عدد ممكن بعد التخفيف لزيادة الفرص
90
  top_candidates = initial_candidates[:600]
91
  enriched_data = await self._fetch_technical_data_batch(top_candidates)
92
 
93
- final_list = []
94
 
 
95
  for item in enriched_data:
96
- # 3. التصنيف الجديد: قاع آمن أم انفجار وشيك؟
97
  classification = self._classify_opportunity_type(item)
98
 
99
  if classification['type'] != 'NONE':
100
- # تشخيص حالة السوق العامة (Regime)
101
  regime_info = self._diagnose_asset_regime(item)
102
- current_regime = regime_info['regime']
103
-
104
- # 🔥 Regime Gating: الحماية من المصائد
105
- # إذا السوق عرضي (RANGE) أو ميت (DEAD)، نمنع صفقات الزخم (MOMENTUM) لأنها غالباً مصائد
106
- if current_regime in ['RANGE', 'DEAD'] and classification['type'] == 'MOMENTUM_LAUNCH':
107
- continue
108
-
109
- # إضافة البيانات
110
- item['asset_regime'] = current_regime
111
  item['asset_regime_conf'] = regime_info['conf']
112
 
113
- # 🏷️ الوسم الجديد للاستراتيجية
114
  item['strategy_type'] = classification['type']
115
  item['l1_sort_score'] = classification['score']
116
-
117
- # حقن العتبات الديناميكية
118
- if self.adaptive_hub_ref:
119
- dynamic_config = self.adaptive_hub_ref.get_regime_config(current_regime)
120
- item['dynamic_limits'] = dynamic_config
121
-
122
- # حفظ التاج القديم للتوافق
123
  item['strategy_tag'] = classification['type']
124
 
125
- final_list.append(item)
 
 
 
 
126
 
127
- # 4. الترتيب النهائي
128
- final_list.sort(key=lambda x: x['l1_sort_score'], reverse=True)
 
 
 
 
129
 
130
- # نمرر عدد أكبر للمعالجات اللاحقة
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  selection = final_list[:60]
132
- print(f"✅ [Layer 1] Passed {len(selection)} candidates (Types: Bottom/Launch).")
 
133
  return selection
134
 
135
  # ==================================================================
136
- # ⚖️ The Dual-Classifier Logic (RELAXED CONDITIONS)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  # ==================================================================
138
  def _classify_opportunity_type(self, data: Dict[str, Any]) -> Dict[str, Any]:
139
  """
140
- تم تخفيف الشروط لزيادة الحساسية (Sensitivity) وتقليل الرفض الخاطئ (False Negatives).
141
  """
142
  try:
143
  df_1h = self._calc_indicators(data['ohlcv_1h_raw'])
144
  curr = df_1h.iloc[-1]
 
 
 
145
  except: return {'type': 'NONE', 'score': 0}
146
 
147
  # --- المؤشرات الأساسية ---
148
  rsi = curr['rsi']
149
  close = curr['close']
 
150
  ema50 = curr['ema50']
151
  ema200 = curr['ema200'] if 'ema200' in curr else ema50
 
152
 
153
- # Bollinger Bands Check
154
  lower_bb = curr['lower_bb'] if 'lower_bb' in curr else (curr['ema20'] - (2*curr['atr']))
155
  upper_bb = curr['upper_bb'] if 'upper_bb' in curr else (curr['ema20'] + (2*curr['atr']))
156
-
157
- # 🛡️ TYPE 1: SAFE_BOTTOM (Relaxed)
158
- # القديم: RSI < 35. الجديد: RSI < 42 (القيعان في الترند الصاعد لا تصل لـ 30 غالباً)
159
- if rsi < 42:
160
- # التأكد من الابتعاد عن المتوسط
 
 
 
 
 
161
  dist_from_ema = (ema50 - close) / ema50
162
-
163
- # السماح بمسافة أكبر قليلاً عن الخط السفلي (3% بدلاً من 1.5%)
164
- if close <= lower_bb * 1.03 and dist_from_ema > 0.02:
165
- # Score adjustment
166
- score = (50 - rsi) / 20.0
167
  return {'type': 'SAFE_BOTTOM', 'score': min(score, 1.0)}
168
 
169
- # 🚀 TYPE 2: MOMENTUM_LAUNCH (Relaxed)
170
- # القديم: 55-75. الجديد: 50-80 (للسماح بدخول مبكر أو متأخر قليلاً)
171
- elif 50 < rsi < 80:
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  if close > ema50 and close > ema200:
173
- # السعر يضغط قرب الحد العلوي
174
  dist_to_upper = (upper_bb - close) / close
175
-
176
- # توسيع مجال الـ Squeeze المسموح به إلى 6% بدلاً من 3%
177
- if dist_to_upper < 0.06:
178
  score = rsi / 100.0
179
  return {'type': 'MOMENTUM_LAUNCH', 'score': score}
180
 
@@ -185,7 +276,7 @@ class DataManager:
185
  # ==================================================================
186
  async def _stage0_universe_filter(self) -> List[Dict[str, Any]]:
187
  try:
188
- print(" 🛡️ [Stage 0] Fetching Tickers (Relaxed Mode)...")
189
  tickers = await self.exchange.fetch_tickers()
190
  candidates = []
191
 
@@ -201,21 +292,18 @@ class DataManager:
201
  reject_stats["blacklist"] += 1
202
  continue
203
 
204
- # حساب الحجم
205
  base_vol = float(ticker.get('baseVolume') or 0.0)
206
  last_price = float(ticker.get('last') or 0.0)
207
  calc_quote_vol = base_vol * last_price
208
 
209
  is_sovereign = symbol in SOVEREIGN_COINS
210
 
211
- # ⬇️ تخفيف فلتر السيولة: من 500k إلى 150k
212
- # ��ذا يسمح باكتشاف الجواهر قبل ارتفاعها الكبير
213
  if not is_sovereign:
214
  if calc_quote_vol < 150000:
215
  reject_stats["volume"] += 1
216
  continue
217
 
218
- # ⬇️ تخفيف فلتر التذبذب: من 25% إلى 35%
219
  change_pct = ticker.get('percentage')
220
  if change_pct is None: change_pct = 0.0
221
 
@@ -256,7 +344,7 @@ class DataManager:
256
  regime = "RANGE"
257
  conf = 0.5
258
 
259
- if atr_pct < 0.5: return {'regime': 'DEAD', 'conf': 0.9}
260
 
261
  if price > ema20 and ema20 > ema50 and rsi > 50:
262
  regime = "BULL"
@@ -327,4 +415,5 @@ class DataManager:
327
 
328
  async def get_order_book_snapshot(self, symbol, limit=20):
329
  try: return await self.exchange.fetch_order_book(symbol, limit)
330
- except: return {}
 
 
1
+ {
2
+ type: uploaded file
3
+ fileName: data_manager.py
4
+ fullContent:
5
  # ============================================================
6
  # 📂 ml_engine/data_manager.py
7
+ # (V62.0 - GEM-Architect: Accumulation Depth & Vitality Check)
8
  # ============================================================
9
 
10
  import asyncio
 
14
  import logging
15
  import pandas as pd
16
  import numpy as np
17
+ from typing import List, Dict, Any, Optional
18
 
19
  # محاولة استيراد حدود النظام
20
  try:
 
48
  'UP', 'DOWN', 'BEAR', 'BULL', '3S', '3L'
49
  ]
50
 
51
+ print(f"📦 [DataManager V62.0] Accumulation Depth & Vitality Check Active.")
52
 
53
  async def initialize(self):
54
  print(" > [DataManager] Starting initialization...")
 
77
  return self.contracts_db
78
 
79
  # ==================================================================
80
+ # 🧠 Layer 1: Classification (Bottom, Momentum, Accumulation)
81
  # ==================================================================
82
  async def layer1_rapid_screening(self, adaptive_hub_ref=None) -> List[Dict[str, Any]]:
83
  self.adaptive_hub_ref = adaptive_hub_ref
84
+ print(f"🔍 [Layer 1] Screening for Safe Bottoms, Accumulation & Momentum...")
85
 
86
+ # 1. فلتر السيولة الأساسي
87
  initial_candidates = await self._stage0_universe_filter()
88
  if not initial_candidates:
89
  print("⚠️ [Layer 1] Stage 0 returned 0 candidates.")
90
  return []
91
 
92
+ # 2. جلب البيانات الفنية (دفعة أكبر لزيادة الفرص)
 
93
  top_candidates = initial_candidates[:600]
94
  enriched_data = await self._fetch_technical_data_batch(top_candidates)
95
 
96
+ semi_final_list = []
97
 
98
+ # 3. التصنيف الفني الأولي
99
  for item in enriched_data:
 
100
  classification = self._classify_opportunity_type(item)
101
 
102
  if classification['type'] != 'NONE':
103
+ # تشخيص حالة السوق
104
  regime_info = self._diagnose_asset_regime(item)
105
+ item['asset_regime'] = regime_info['regime']
 
 
 
 
 
 
 
 
106
  item['asset_regime_conf'] = regime_info['conf']
107
 
108
+ # تخزين نوع الاستراتيجية والنتيجة الأولية
109
  item['strategy_type'] = classification['type']
110
  item['l1_sort_score'] = classification['score']
 
 
 
 
 
 
 
111
  item['strategy_tag'] = classification['type']
112
 
113
+ # فحص الـ Regime (نرفض الانفجارات في الأسواق الميتة، لكن نقبل التجميع)
114
+ if regime_info['regime'] == 'DEAD' and classification['type'] == 'MOMENTUM_LAUNCH':
115
+ # استثناء: إذا كان "Squeeze" (تجميع)، نسمح بمروره حتى لو السوق ميت
116
+ if not classification.get('is_squeeze', False):
117
+ continue
118
 
119
+ semi_final_list.append(item)
120
+
121
+ # 4. 🧱 فحص عمق السوق (Order Book Check) للمرشحين للتجميع
122
+ # هذا هو "مصدر الكشف" الجديد الذي طلبته
123
+ final_list = []
124
+ print(f" 🛡️ [Layer 1.5] Checking Depth Support for {len(semi_final_list)} candidates...")
125
 
126
+ # نأخذ أفضل 50 مرشحاً فقط لفحص دفتر الطلبات لتجنب حظر API
127
+ semi_final_list.sort(key=lambda x: x['l1_sort_score'], reverse=True)
128
+ candidates_for_depth = semi_final_list[:50]
129
+
130
+ for item in candidates_for_depth:
131
+ # إذا كانت تجميع (Squeeze) أو قاع، نفحص هل هناك دعم حقيقي في الأسفل؟
132
+ if item['strategy_type'] in ['ACCUMULATION_SQUEEZE', 'SAFE_BOTTOM']:
133
+ try:
134
+ # حساب نطاق الساعتين القادمتين (2 * ATR)
135
+ atr_val = item.get('atr_value', 0.0)
136
+ curr_price = item.get('current_price', 0.0)
137
+
138
+ if atr_val > 0 and curr_price > 0:
139
+ range_2h = atr_val * 2.0
140
+ # فحص ضغط دفتر الطلبات في هذا النطاق
141
+ ob_score = await self._check_ob_pressure(item['symbol'], curr_price, range_2h)
142
+
143
+ # تعزيز النتيجة بناءً على دعم دفتر الطلبات
144
+ if ob_score > 0.6: # دعم قوي
145
+ item['l1_sort_score'] += 0.15 # بونص
146
+ item['note'] = f"Strong Depth Support ({ob_score:.2f})"
147
+ elif ob_score < 0.4: # دعم ضعيف
148
+ item['l1_sort_score'] -= 0.10 # عقوبة
149
+ except Exception: pass
150
+
151
+ # حقن الإعدادات الديناميكية
152
+ if self.adaptive_hub_ref:
153
+ dynamic_config = self.adaptive_hub_ref.get_regime_config(item['asset_regime'])
154
+ item['dynamic_limits'] = dynamic_config
155
+
156
+ final_list.append(item)
157
+
158
+ # 5. الترتيب النهائي والإرجاع
159
+ final_list.sort(key=lambda x: x['l1_sort_score'], reverse=True)
160
  selection = final_list[:60]
161
+
162
+ print(f"✅ [Layer 1] Passed {len(selection)} candidates (With Depth Verification).")
163
  return selection
164
 
165
  # ==================================================================
166
+ # 🧱 Order Book Depth Scanner (2-Hour Range Logic)
167
+ # ==================================================================
168
+ async def _check_ob_pressure(self, symbol: str, current_price: float, price_range: float) -> float:
169
+ """
170
+ يفحص دفتر الطلبات داخل النطاق السعري المتوقع للساعتين القادمتين.
171
+ النطاق = السعر الحالي +/- (ATR_1H * 2)
172
+ يعيد نسبة قوة المشترين (0.0 إلى 1.0).
173
+ """
174
+ try:
175
+ # جلب لقطة سريعة لدفتر الطلبات (50 مستوى كافية للتجميع القريب)
176
+ ob = await self.exchange.fetch_order_book(symbol, limit=50)
177
+
178
+ bids = ob['bids']
179
+ asks = ob['asks']
180
+
181
+ min_price = current_price - price_range
182
+ max_price = current_price + price_range
183
+
184
+ support_vol = 0.0
185
+ resistance_vol = 0.0
186
+
187
+ # تجميع طلبات الشراء داخل نطاق الدعم المتوقع
188
+ for p, v in bids:
189
+ if p >= min_price:
190
+ support_vol += v
191
+ else: break # بما أن Bids مرتبة تنازلياً
192
+
193
+ # تجميع طلبات البيع داخل نطاق المقاومة المتوقع
194
+ for p, v in asks:
195
+ if p <= max_price:
196
+ resistance_vol += v
197
+ else: break # بما أن Asks مرتبة تصاعدياً
198
+
199
+ if (support_vol + resistance_vol) == 0: return 0.5
200
+
201
+ # نسبة ضغط الشراء (Imbalance)
202
+ pressure_ratio = support_vol / (support_vol + resistance_vol)
203
+ return pressure_ratio
204
+
205
+ except Exception:
206
+ return 0.5 # حياد في حال الفشل
207
+
208
+ # ==================================================================
209
+ # ⚖️ The Dual-Classifier Logic (GEM-Architect: ACCUMULATION UPGRADE)
210
  # ==================================================================
211
  def _classify_opportunity_type(self, data: Dict[str, Any]) -> Dict[str, Any]:
212
  """
213
+ تم التعديل ليشمل منطق التجميع (RSI 45-60) وفلتر الحياة (ATR).
214
  """
215
  try:
216
  df_1h = self._calc_indicators(data['ohlcv_1h_raw'])
217
  curr = df_1h.iloc[-1]
218
+
219
+ # تخزين ATR لاستخدامه لاحقاً في فحص دفتر الطلبات
220
+ data['atr_value'] = curr['atr']
221
  except: return {'type': 'NONE', 'score': 0}
222
 
223
  # --- المؤشرات الأساسية ---
224
  rsi = curr['rsi']
225
  close = curr['close']
226
+ ema20 = curr['ema20']
227
  ema50 = curr['ema50']
228
  ema200 = curr['ema200'] if 'ema200' in curr else ema50
229
+ atr = curr['atr']
230
 
231
+ # Bollinger Bands
232
  lower_bb = curr['lower_bb'] if 'lower_bb' in curr else (curr['ema20'] - (2*curr['atr']))
233
  upper_bb = curr['upper_bb'] if 'upper_bb' in curr else (curr['ema20'] + (2*curr['atr']))
234
+ bb_width = (upper_bb - lower_bb) / curr['ema20'] if curr['ema20'] > 0 else 1.0
235
+
236
+ # 🔥 1. Dead Coin Filter (فلتر العملات الميتة)
237
+ # نحسب نسبة الـ ATR للسعر. إذا كانت الحركة أقل من 0.4% في الساعة، نرفضها.
238
+ volatility_pct = (atr / close) * 100 if close > 0 else 0
239
+ if volatility_pct < 0.4:
240
+ return {'type': 'NONE', 'score': 0}
241
+
242
+ # 🛡️ TYPE 1: SAFE_BOTTOM (RSI < 45)
243
+ if rsi < 45:
244
  dist_from_ema = (ema50 - close) / ema50
245
+ # السماح بمسافة أكبر قليلاً عن الخط السفلي (5%)
246
+ if close <= lower_bb * 1.05 and dist_from_ema > 0.015:
247
+ score = (55 - rsi) / 20.0
 
 
248
  return {'type': 'SAFE_BOTTOM', 'score': min(score, 1.0)}
249
 
250
+ # 🔋 TYPE 2: ACCUMULATION_SQUEEZE (The Missing Link)
251
+ # المنطق: RSI محايد (45-60) + قناة ضيقة (Squeeze) + دعم دفتر الطلبات (سيفحص لاحقاً)
252
+ elif 45 <= rsi <= 60:
253
+ if bb_width < 0.12: # وسعنا النطاق قليلاً لـ 12% لزيادة المرونة
254
+ # نفضل أن يكون السعر يحاول الصعود فوق متوسط 20
255
+ if close > ema20 * 0.995: # السماح بكسر وهمي بسيط
256
+ score = 1.0 - (bb_width * 4.0)
257
+ # نستخدم وسم خاص لتمييزها
258
+ return {
259
+ 'type': 'ACCUMULATION_SQUEEZE',
260
+ 'score': max(score, 0.5),
261
+ 'is_squeeze': True
262
+ }
263
+
264
+ # 🚀 TYPE 3: MOMENTUM_LAUNCH (Classic)
265
+ elif 60 < rsi < 80:
266
  if close > ema50 and close > ema200:
 
267
  dist_to_upper = (upper_bb - close) / close
268
+ if dist_to_upper < 0.08:
 
 
269
  score = rsi / 100.0
270
  return {'type': 'MOMENTUM_LAUNCH', 'score': score}
271
 
 
276
  # ==================================================================
277
  async def _stage0_universe_filter(self) -> List[Dict[str, Any]]:
278
  try:
279
+ print(" 🛡️ [Stage 0] Fetching Tickers (Accumulation Mode)...")
280
  tickers = await self.exchange.fetch_tickers()
281
  candidates = []
282
 
 
292
  reject_stats["blacklist"] += 1
293
  continue
294
 
 
295
  base_vol = float(ticker.get('baseVolume') or 0.0)
296
  last_price = float(ticker.get('last') or 0.0)
297
  calc_quote_vol = base_vol * last_price
298
 
299
  is_sovereign = symbol in SOVEREIGN_COINS
300
 
301
+ # تخفيف فلتر السيولة للسماح بعملات التجميع الصغيرة
 
302
  if not is_sovereign:
303
  if calc_quote_vol < 150000:
304
  reject_stats["volume"] += 1
305
  continue
306
 
 
307
  change_pct = ticker.get('percentage')
308
  if change_pct is None: change_pct = 0.0
309
 
 
344
  regime = "RANGE"
345
  conf = 0.5
346
 
347
+ if atr_pct < 0.4: return {'regime': 'DEAD', 'conf': 0.9}
348
 
349
  if price > ema20 and ema20 > ema50 and rsi > 50:
350
  regime = "BULL"
 
415
 
416
  async def get_order_book_snapshot(self, symbol, limit=20):
417
  try: return await self.exchange.fetch_order_book(symbol, limit)
418
+ except: return {}
419
+ }