Riy777 commited on
Commit
1766ccb
·
verified ·
1 Parent(s): a8149e3

Update ml_engine/data_manager.py

Browse files
Files changed (1) hide show
  1. ml_engine/data_manager.py +35 -41
ml_engine/data_manager.py CHANGED
@@ -1,6 +1,6 @@
1
  # ============================================================
2
  # 📂 ml_engine/data_manager.py
3
- # (V61.0 - GEM-Architect: Dual-Opportunity Classification)
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 V61.0] Dual Classification (Safe Bottoms & Momentum) Active.")
48
 
49
  async def initialize(self):
50
  print(" > [DataManager] Starting initialization...")
@@ -79,14 +79,15 @@ class DataManager:
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
- top_candidates = initial_candidates[:500]
 
90
  enriched_data = await self._fetch_technical_data_batch(top_candidates)
91
 
92
  final_list = []
@@ -96,13 +97,12 @@ class DataManager:
96
  classification = self._classify_opportunity_type(item)
97
 
98
  if classification['type'] != 'NONE':
99
- # تشخيص حالة السوق العامة (Regime) يبقى كما هو للأمان العام
100
  regime_info = self._diagnose_asset_regime(item)
101
  current_regime = regime_info['regime']
102
 
103
  # 🔥 Regime Gating: الحماية من المصائد
104
  # إذا السوق عرضي (RANGE) أو ميت (DEAD)، نمنع صفقات الزخم (MOMENTUM) لأنها غالباً مصائد
105
- # لكن نسمح بصفقات القيعان (SAFE_BOTTOM) لأنها آمنة في التذبذب
106
  if current_regime in ['RANGE', 'DEAD'] and classification['type'] == 'MOMENTUM_LAUNCH':
107
  continue
108
 
@@ -127,23 +127,20 @@ class DataManager:
127
  # 4. الترتيب النهائي
128
  final_list.sort(key=lambda x: x['l1_sort_score'], reverse=True)
129
 
130
- selection = final_list[:50]
 
131
  print(f"✅ [Layer 1] Passed {len(selection)} candidates (Types: Bottom/Launch).")
132
  return selection
133
 
134
  # ==================================================================
135
- # ⚖️ The Dual-Classifier Logic
136
  # ==================================================================
137
  def _classify_opportunity_type(self, data: Dict[str, Any]) -> Dict[str, Any]:
138
  """
139
- تقسيم العملات إلى نوعين:
140
- 1. SAFE_BOTTOM: عملات في القاع (آمنة من الهبوط الحاد).
141
- 2. MOMENTUM_LAUNCH: عملات تجهز للانفجار (نسبة 90%).
142
  """
143
  try:
144
  df_1h = self._calc_indicators(data['ohlcv_1h_raw'])
145
- # يمكن استخدام فريم 15 دقيقة للتأكيد الإضافي مستقبلاً
146
- # df_15m = self._calc_indicators(data['ohlcv_15m_raw'])
147
  curr = df_1h.iloc[-1]
148
  except: return {'type': 'NONE', 'score': 0}
149
 
@@ -157,44 +154,41 @@ class DataManager:
157
  lower_bb = curr['lower_bb'] if 'lower_bb' in curr else (curr['ema20'] - (2*curr['atr']))
158
  upper_bb = curr['upper_bb'] if 'upper_bb' in curr else (curr['ema20'] + (2*curr['atr']))
159
 
160
- # 🛡️ TYPE 1: SAFE_BOTTOM (The Safety Net)
161
- # الشروط: RSI منخفض جداً، السعر عند أو تحت الحد السفلي، بعيد عن المتوسطات
162
- # هذا النوع آمن لأن الهبوط الإضافي صعب إحصائياً (Mean Reversion)
163
- if rsi < 35:
164
- # التأكد من الابتعاد عن المتوسط (Oversold Extension)
165
  dist_from_ema = (ema50 - close) / ema50
166
 
167
- # السعر عند الدعم السفلي أو تحته بقليل
168
- if close <= lower_bb * 1.015 and dist_from_ema > 0.03:
169
- # Score: كلما كان RSI أقل، كلما كان الارتداد أقوى
170
  score = (50 - rsi) / 20.0
171
  return {'type': 'SAFE_BOTTOM', 'score': min(score, 1.0)}
172
 
173
- # 🚀 TYPE 2: MOMENTUM_LAUNCH (The Rocket)
174
- # الشروط: RSI صحي وقوي (فوق 55 وتحت 75)، السعر فوق المتوسطات، ضغط سعري (Squeeze)
175
- # هذا النوع يجهز للانفجار للأعلى
176
- elif 55 < rsi < 75:
177
  if close > ema50 and close > ema200:
178
- # السعر يضغط قرب الحد العلوي (Squeeze before breakout)
179
  dist_to_upper = (upper_bb - close) / close
180
 
181
- # قريب جداً من الانفجار (أقل من 3% للحد العلوي)
182
- if dist_to_upper < 0.03:
183
  score = rsi / 100.0
184
  return {'type': 'MOMENTUM_LAUNCH', 'score': score}
185
 
186
  return {'type': 'NONE', 'score': 0}
187
 
188
  # ==================================================================
189
- # 🔍 Stage 0: Universe Filter (Robust USD Calc)
190
  # ==================================================================
191
  async def _stage0_universe_filter(self) -> List[Dict[str, Any]]:
192
  try:
193
- print(" 🛡️ [Stage 0] Fetching Tickers...")
194
  tickers = await self.exchange.fetch_tickers()
195
  candidates = []
196
 
197
- # القائمة السيادية (تمر دائماً)
198
  SOVEREIGN_COINS = ['BTC/USDT', 'ETH/USDT', 'SOL/USDT', 'BNB/USDT', 'XRP/USDT']
199
 
200
  reject_stats = {"volume": 0, "change": 0, "blacklist": 0}
@@ -207,25 +201,25 @@ class DataManager:
207
  reject_stats["blacklist"] += 1
208
  continue
209
 
210
- # حساب الحجم يدوياً لضمان الدقة
211
  base_vol = float(ticker.get('baseVolume') or 0.0)
212
  last_price = float(ticker.get('last') or 0.0)
213
  calc_quote_vol = base_vol * last_price
214
 
215
  is_sovereign = symbol in SOVEREIGN_COINS
216
 
217
- # فلتر السيولة (500k) - نتجاوزه للعملات السيادية
 
218
  if not is_sovereign:
219
- if calc_quote_vol < 500000:
220
  reject_stats["volume"] += 1
221
  continue
222
 
223
- # فلتر التغير (20%) - نتجاوزه إذا كنا نبحث عن قيعان (قد تكون هابطة) لكن بحذر
224
- # هنا نبقي الفلتر العام للحماية من الانهيارات التامة
225
  change_pct = ticker.get('percentage')
226
  if change_pct is None: change_pct = 0.0
227
 
228
- if abs(change_pct) > 25.0:
229
  reject_stats["change"] += 1
230
  continue
231
 
@@ -275,7 +269,7 @@ class DataManager:
275
  except Exception: return {'regime': 'RANGE', 'conf': 0.0}
276
 
277
  # ------------------------------------------------------------------
278
- # Helpers & Indicators (Updated for BB & EMA200)
279
  # ------------------------------------------------------------------
280
  async def _fetch_technical_data_batch(self, candidates):
281
  chunk_size = 10; results = []
@@ -289,7 +283,7 @@ class DataManager:
289
 
290
  async def _fetch_single(self, c):
291
  try:
292
- h1 = await self.exchange.fetch_ohlcv(c['symbol'], '1h', limit=210) # زاد العدد لحساب EMA200 بدقة
293
  m15 = await self.exchange.fetch_ohlcv(c['symbol'], '15m', limit=60)
294
  if not h1 or not m15: return None
295
  c['ohlcv'] = {'1h': h1, '15m': m15}
@@ -309,7 +303,7 @@ class DataManager:
309
  # EMAs
310
  df['ema20'] = df['c'].ewm(span=20).mean()
311
  df['ema50'] = df['c'].ewm(span=50).mean()
312
- df['ema200'] = df['c'].ewm(span=200).mean() # مهم لنوع الزخم
313
 
314
  # ATR
315
  tr = np.maximum(df['h']-df['l'], np.maximum(abs(df['h']-df['c'].shift()), abs(df['l']-df['c'].shift())))
 
1
  # ============================================================
2
  # 📂 ml_engine/data_manager.py
3
+ # (V61.1 - GEM-Architect: Relaxed Filtering & Dual Classification)
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 V61.1] Relaxed Filtering Active.")
48
 
49
  async def initialize(self):
50
  print(" > [DataManager] Starting initialization...")
 
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 = []
 
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
 
 
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
 
 
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
 
181
  return {'type': 'NONE', 'score': 0}
182
 
183
  # ==================================================================
184
+ # 🔍 Stage 0: Universe Filter (RELAXED USD CALC)
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
 
 
192
  SOVEREIGN_COINS = ['BTC/USDT', 'ETH/USDT', 'SOL/USDT', 'BNB/USDT', 'XRP/USDT']
193
 
194
  reject_stats = {"volume": 0, "change": 0, "blacklist": 0}
 
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
 
222
+ if abs(change_pct) > 35.0:
223
  reject_stats["change"] += 1
224
  continue
225
 
 
269
  except Exception: return {'regime': 'RANGE', 'conf': 0.0}
270
 
271
  # ------------------------------------------------------------------
272
+ # Helpers & Indicators
273
  # ------------------------------------------------------------------
274
  async def _fetch_technical_data_batch(self, candidates):
275
  chunk_size = 10; results = []
 
283
 
284
  async def _fetch_single(self, c):
285
  try:
286
+ h1 = await self.exchange.fetch_ohlcv(c['symbol'], '1h', limit=210)
287
  m15 = await self.exchange.fetch_ohlcv(c['symbol'], '15m', limit=60)
288
  if not h1 or not m15: return None
289
  c['ohlcv'] = {'1h': h1, '15m': m15}
 
303
  # EMAs
304
  df['ema20'] = df['c'].ewm(span=20).mean()
305
  df['ema50'] = df['c'].ewm(span=50).mean()
306
+ df['ema200'] = df['c'].ewm(span=200).mean()
307
 
308
  # ATR
309
  tr = np.maximum(df['h']-df['l'], np.maximum(abs(df['h']-df['c'].shift()), abs(df['l']-df['c'].shift())))