Riy777 commited on
Commit
c81aa0a
·
verified ·
1 Parent(s): 8878c1d

Update ml_engine/data_manager.py

Browse files
Files changed (1) hide show
  1. ml_engine/data_manager.py +93 -71
ml_engine/data_manager.py CHANGED
@@ -1,6 +1,6 @@
1
  # ============================================================
2
  # 📂 ml_engine/data_manager.py
3
- # (V60.4 - GEM-Architect: Regime Gating + Robust Data)
4
  # ============================================================
5
 
6
  import asyncio
@@ -44,7 +44,7 @@ class DataManager:
44
  'UP', 'DOWN', 'BEAR', 'BULL', '3S', '3L'
45
  ]
46
 
47
- print(f"📦 [DataManager V60.4] Regime Gating (Range Protection) Active.")
48
 
49
  async def initialize(self):
50
  print(" > [DataManager] Starting initialization...")
@@ -73,13 +73,13 @@ class DataManager:
73
  return self.contracts_db
74
 
75
  # ==================================================================
76
- # 🧠 Layer 1: Screening + Diagnosis + Regime Gating
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 with Regime Gating...")
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.")
@@ -92,41 +92,99 @@ class DataManager:
92
  final_list = []
93
 
94
  for item in enriched_data:
95
- # 3. التصنيف الفني (Breakout vs Reversal)
96
- classification = self._apply_strict_logic_tree(item)
97
 
98
  if classification['type'] != 'NONE':
99
- # 4. التشخيص (Diagnosis)
100
  regime_info = self._diagnose_asset_regime(item)
101
  current_regime = regime_info['regime']
102
 
103
- # 🔥 5. Regime Gating (بوابة النظام - الحماية من المصيدة)
104
- # إذا السوق عرضي (RANGE) أو ميت (DEAD)، نمنع الاختراقات (BREAKOUT)
105
- # لأن الاختراقات تفشل في هذه الظروف وتصبح مصيدة ثيران.
106
- if current_regime in ['RANGE', 'DEAD'] and classification['type'] == 'BREAKOUT':
107
- # تخطي بصمت (حماية)
108
- continue
109
 
110
- # إذا مر من البوابة، نتابع
111
  item['asset_regime'] = current_regime
112
  item['asset_regime_conf'] = regime_info['conf']
113
 
114
- # حقن العتبات
 
 
 
 
115
  if self.adaptive_hub_ref:
116
  dynamic_config = self.adaptive_hub_ref.get_regime_config(current_regime)
117
  item['dynamic_limits'] = dynamic_config
118
 
119
- item['l1_sort_score'] = classification['score']
120
  item['strategy_tag'] = classification['type']
 
121
  final_list.append(item)
122
 
123
- # 6. الترتيب النهائي
124
  final_list.sort(key=lambda x: x['l1_sort_score'], reverse=True)
125
 
126
  selection = final_list[:50]
127
- print(f"✅ [Layer 1] Passed {len(selection)} candidates (Safe Strategies Only).")
128
  return selection
129
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  # ==================================================================
131
  # 🔍 Stage 0: Universe Filter (Robust USD Calc)
132
  # ==================================================================
@@ -140,7 +198,6 @@ class DataManager:
140
  SOVEREIGN_COINS = ['BTC/USDT', 'ETH/USDT', 'SOL/USDT', 'BNB/USDT', 'XRP/USDT']
141
 
142
  reject_stats = {"volume": 0, "change": 0, "blacklist": 0}
143
- debug_printed = False
144
 
145
  for symbol, ticker in tickers.items():
146
  if not symbol.endswith('/USDT'): continue
@@ -157,22 +214,18 @@ class DataManager:
157
 
158
  is_sovereign = symbol in SOVEREIGN_COINS
159
 
160
- # طباعة فحص BTC مرة واحدة
161
- if "BTC/USDT" in symbol and not debug_printed:
162
- print(f" 🐛 [DEBUG] BTC Vol: ${calc_quote_vol:,.0f}")
163
- debug_printed = True
164
-
165
  # فلتر السيولة (500k) - نتجاوزه للعملات السيادية
166
  if not is_sovereign:
167
  if calc_quote_vol < 500000:
168
  reject_stats["volume"] += 1
169
  continue
170
 
171
- # فلتر التغير (20%)
 
172
  change_pct = ticker.get('percentage')
173
  if change_pct is None: change_pct = 0.0
174
 
175
- if abs(change_pct) > 20.0:
176
  reject_stats["change"] += 1
177
  continue
178
 
@@ -183,9 +236,6 @@ class DataManager:
183
  'change_24h': change_pct
184
  })
185
 
186
- print(f" 📊 [Filter Stats] Total: {len(tickers)} | Passed: {len(candidates)}")
187
- print(f" ❌ Rejected: Vol < 500k ({reject_stats['volume']}) | Change > 20% ({reject_stats['change']})")
188
-
189
  candidates.sort(key=lambda x: x['quote_volume'], reverse=True)
190
  return candidates
191
 
@@ -225,46 +275,7 @@ class DataManager:
225
  except Exception: return {'regime': 'RANGE', 'conf': 0.0}
226
 
227
  # ------------------------------------------------------------------
228
- # 🛡️ The Logic Tree (Anti-FOMO Tuned)
229
- # ------------------------------------------------------------------
230
- def _apply_strict_logic_tree(self, data: Dict[str, Any]) -> Dict[str, Any]:
231
- try:
232
- df_1h = self._calc_indicators(data['ohlcv_1h_raw'])
233
- df_15m = self._calc_indicators(data['ohlcv_15m_raw'])
234
- data['df_1h'] = df_1h
235
- except: return {'type': 'NONE', 'score': 0}
236
-
237
- curr_1h = df_1h.iloc[-1]
238
- curr_15m = df_15m.iloc[-1]
239
-
240
- try:
241
- close_4h_ago = df_1h.iloc[-5]['close']
242
- change_4h = ((curr_1h['close'] - close_4h_ago) / close_4h_ago) * 100
243
- except: change_4h = 0.0
244
-
245
- # Gates
246
- if change_4h > 12.0: return {'type': 'NONE', 'score': 0}
247
- if curr_1h['rsi'] > 75: return {'type': 'NONE', 'score': 0}
248
- dev = (curr_1h['close'] - curr_1h['ema20']) / curr_1h['atr'] if curr_1h['atr'] > 0 else 0
249
- if dev > 2.2: return {'type': 'NONE', 'score': 0}
250
-
251
- # A. Breakout
252
- is_bullish = (curr_1h['ema20'] > curr_1h['ema50']) or (curr_1h['close'] > curr_1h['ema20'])
253
- if is_bullish and (45 <= curr_1h['rsi'] <= 75):
254
- vol_ma = df_15m['volume'].rolling(20).mean().iloc[-1]
255
- if curr_15m['volume'] >= 1.2 * vol_ma:
256
- score = curr_15m['volume'] / vol_ma if vol_ma > 0 else 1.0
257
- return {'type': 'BREAKOUT', 'score': score}
258
-
259
- # B. Reversal
260
- if 20 <= curr_1h['rsi'] <= 40 and change_4h <= -2.0:
261
- score = (100 - curr_1h['rsi'])
262
- return {'type': 'REVERSAL', 'score': score}
263
-
264
- return {'type': 'NONE', 'score': 0}
265
-
266
- # ------------------------------------------------------------------
267
- # Helpers
268
  # ------------------------------------------------------------------
269
  async def _fetch_technical_data_batch(self, candidates):
270
  chunk_size = 10; results = []
@@ -278,7 +289,7 @@ class DataManager:
278
 
279
  async def _fetch_single(self, c):
280
  try:
281
- h1 = await self.exchange.fetch_ohlcv(c['symbol'], '1h', limit=60)
282
  m15 = await self.exchange.fetch_ohlcv(c['symbol'], '15m', limit=60)
283
  if not h1 or not m15: return None
284
  c['ohlcv'] = {'1h': h1, '15m': m15}
@@ -294,10 +305,21 @@ class DataManager:
294
  loss = (-delta.where(delta<0, 0)).rolling(14).mean()
295
  rs = gain/loss
296
  df['rsi'] = 100 - (100/(1+rs))
 
 
297
  df['ema20'] = df['c'].ewm(span=20).mean()
298
  df['ema50'] = df['c'].ewm(span=50).mean()
 
 
 
299
  tr = np.maximum(df['h']-df['l'], np.maximum(abs(df['h']-df['c'].shift()), abs(df['l']-df['c'].shift())))
300
  df['atr'] = tr.rolling(14).mean()
 
 
 
 
 
 
301
  df.rename(columns={'o':'open', 'h':'high', 'l':'low', 'c':'close', 'v':'volume'}, inplace=True)
302
  return df.fillna(0)
303
 
 
1
  # ============================================================
2
  # 📂 ml_engine/data_manager.py
3
+ # (V61.0 - GEM-Architect: Dual-Opportunity Classification)
4
  # ============================================================
5
 
6
  import asyncio
 
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...")
 
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.")
 
92
  final_list = []
93
 
94
  for item in enriched_data:
95
+ # 3. التصنيف الجديد: قاع آمن أم انفجار وشيك؟
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
 
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
  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
+
150
+ # --- المؤشرات الأساسية ---
151
+ rsi = curr['rsi']
152
+ close = curr['close']
153
+ ema50 = curr['ema50']
154
+ ema200 = curr['ema200'] if 'ema200' in curr else ema50
155
+
156
+ # Bollinger Bands Check
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
  # ==================================================================
 
198
  SOVEREIGN_COINS = ['BTC/USDT', 'ETH/USDT', 'SOL/USDT', 'BNB/USDT', 'XRP/USDT']
199
 
200
  reject_stats = {"volume": 0, "change": 0, "blacklist": 0}
 
201
 
202
  for symbol, ticker in tickers.items():
203
  if not symbol.endswith('/USDT'): continue
 
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
 
 
236
  'change_24h': change_pct
237
  })
238
 
 
 
 
239
  candidates.sort(key=lambda x: x['quote_volume'], reverse=True)
240
  return candidates
241
 
 
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
 
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}
 
305
  loss = (-delta.where(delta<0, 0)).rolling(14).mean()
306
  rs = gain/loss
307
  df['rsi'] = 100 - (100/(1+rs))
308
+
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())))
316
  df['atr'] = tr.rolling(14).mean()
317
+
318
+ # Bollinger Bands (Standard: 20, 2)
319
+ std = df['c'].rolling(20).std()
320
+ df['upper_bb'] = df['ema20'] + (2 * std)
321
+ df['lower_bb'] = df['ema20'] - (2 * std)
322
+
323
  df.rename(columns={'o':'open', 'h':'high', 'l':'low', 'c':'close', 'v':'volume'}, inplace=True)
324
  return df.fillna(0)
325