Riy777 commited on
Commit
e378be0
·
verified ·
1 Parent(s): b938152

Update ml_engine/data_manager.py

Browse files
Files changed (1) hide show
  1. ml_engine/data_manager.py +59 -69
ml_engine/data_manager.py CHANGED
@@ -1,6 +1,6 @@
1
  # ============================================================
2
  # 📂 ml_engine/data_manager.py
3
- # (V41.3 - GEM-Architect: Math Debugger Edition)
4
  # ============================================================
5
 
6
  import asyncio
@@ -28,9 +28,9 @@ logging.getLogger("ccxt").setLevel(logging.WARNING)
28
 
29
  class DataManager:
30
  """
31
- DataManager V41.3 (Math Debugger)
32
- - Exposes calculation errors in _apply_scanner_strategies.
33
- - Inspects DataFrame integrity.
34
  """
35
 
36
  def __init__(self, contracts_db, whale_monitor, r2_service=None):
@@ -52,7 +52,7 @@ class DataManager:
52
  'UP', 'DOWN', 'BEAR', 'BULL', '3S', '3L', 'USDD', 'USDP'
53
  ]
54
 
55
- print(f"📦 [DataManager V41.3] Math Debugger Online.")
56
 
57
  async def initialize(self):
58
  print(" > [DataManager] Starting initialization...")
@@ -93,31 +93,24 @@ class DataManager:
93
 
94
  print(f"🔍 [L1 Matrix] Regime: {current_regime} | Weights: {scanner_weights}")
95
 
96
- # 1. جلب العملات
97
  all_tickers = await self._fetch_universe_tickers()
98
  if not all_tickers:
99
  print("⚠️ [Layer 1] Universe fetch returned empty.")
100
  return []
101
 
102
- # 2. الجلب العميق (أخذ عينة أكبر قليلاً للتجربة)
103
- top_candidates = all_tickers[:60]
104
  enriched_data = await self._batch_fetch_ta_data(top_candidates, timeframe='15m', limit=100)
105
 
106
  scored_candidates = []
107
  debug_log_sample = []
108
 
109
- # 🔍 فحص سلامة البيانات لأول عملة (Data Integrity Check)
110
- if enriched_data:
111
- first_coin = enriched_data[0]
112
- if 'df' in first_coin:
113
- print(f" -> 📊 [Data Inspect] {first_coin['symbol']} DF Shape: {first_coin['df'].shape}")
114
- print(f" -> 📊 [Data Inspect] Tail:\n{first_coin['df'].tail(3)[['close', 'volume']]}")
115
-
116
  for item in enriched_data:
117
  df = item.get('df')
118
  if df is None or len(df) < 50: continue
119
 
120
- # 🔥 تطبيق الكاشفات (سيتم طباعة الخطأ إذا حدث)
121
  scores = self._apply_scanner_strategies(df, item['symbol'])
122
 
123
  final_score = 0.0
@@ -128,6 +121,7 @@ class DataManager:
128
  final_score += (val['score'] * w)
129
  if val['active']: tags.append(strategy)
130
 
 
131
  if item['change_24h'] > 3.0 and current_regime == "BULL": final_score += 10
132
 
133
  item['l1_score'] = final_score
@@ -160,22 +154,20 @@ class DataManager:
160
  ]
161
 
162
  # ==================================================================
163
- # 🧩 Scanner Strategies Logic (With Error Exposure)
164
  # ==================================================================
165
  def _apply_scanner_strategies(self, df: pd.DataFrame, symbol: str) -> Dict[str, Any]:
166
- """تطبيق المؤشرات مع كشف الأخطاء"""
167
  results = {}
168
  try:
169
- # التأكد من عدم وجود قيم فارغة (NaN)
170
- # ملء الفراغات بآخر قيمة صالحة (Forward Fill)
171
  df = df.ffill().bfill()
172
  close = df['close']
173
 
174
  # 1. RSI
175
  rsi = ta.rsi(close, length=14)
176
- # قد يعود RSI بقيم NaN في البداية، نأخذ القيمة الأخيرة ونتأكد أنها رقم
177
  curr_rsi = rsi.iloc[-1] if rsi is not None else 50.0
178
- if np.isnan(curr_rsi): curr_rsi = 50.0 # حماية ضد NaN
179
 
180
  score_rsi = 0
181
  active_rsi = False
@@ -187,39 +179,54 @@ class DataManager:
187
  score_rsi = 80
188
  active_rsi = True
189
  elif 30 < curr_rsi <= 50:
190
- score_rsi = 40
191
 
192
  results["RSI_MOMENTUM"] = {'score': score_rsi, 'active': active_rsi, 'val': curr_rsi}
193
 
194
- # 2. BB
195
  bb = ta.bbands(close, length=20, std=2)
196
- if bb is not None:
197
- upper = bb[f'BBU_20_2.0'].iloc[-1]
198
- width = bb[f'BBB_20_2.0'].iloc[-1]
199
- curr_price = close.iloc[-1]
200
- score_bb = 0
201
- active_bb = False
202
- if curr_price > upper and width > 0.1:
203
- score_bb = 100
204
- active_bb = True
205
- else:
206
- score_bb = 0; active_bb = False
 
 
 
 
 
 
 
 
 
 
 
207
  results["BB_BREAKOUT"] = {'score': score_bb, 'active': active_bb}
208
 
209
- # 3. MACD
210
  macd = ta.macd(close)
211
- if macd is not None:
212
- hist = macd[f'MACDh_12_26_9'].iloc[-1]
213
- score_macd = 0
214
- active_macd = False
215
- if hist > 0:
216
- score_macd = 100
217
- active_macd = True
218
- else:
219
- score_macd = 0; active_macd = False
 
 
 
 
220
  results["MACD_CROSS"] = {'score': score_macd, 'active': active_macd}
221
 
222
- # 4. Volume
223
  vol = df['volume']
224
  vol_ma = ta.sma(vol, length=20).iloc[-1]
225
  curr_vol = vol.iloc[-1]
@@ -231,46 +238,32 @@ class DataManager:
231
  results["VOLUME_FLOW"] = {'score': score_vol, 'active': active_vol}
232
 
233
  except Exception as e:
234
- # 🔥 طباعة الخطأ الحقيقي هنا 🔥
235
  print(f"❌ [Scanner Error] {symbol}: {e}")
236
- # traceback.print_exc() # قم بتفعيل هذا إذا أردت التفاصيل المملة
237
  return {k: {'score': 0, 'active': False, 'val': 0} for k in ["RSI_MOMENTUM", "BB_BREAKOUT", "MACD_CROSS", "VOLUME_FLOW"]}
238
 
239
  return results
240
 
241
  # ==================================================================
242
- # 🌍 Universe & Batch Fetch (Fixed)
243
  # ==================================================================
244
  async def _fetch_universe_tickers(self) -> List[Dict[str, Any]]:
245
- print(" -> 📡 [Debug] Contacting Exchange for Tickers...")
246
  try:
247
  tickers = await self.exchange.fetch_tickers()
248
- print(f" -> 📡 [Debug] Raw Tickers Received: {len(tickers)}")
249
-
250
  candidates = []
251
- skipped_reason = {"pair": 0, "blacklist": 0, "volume": 0}
252
 
253
  for symbol, ticker in tickers.items():
254
- if not symbol.endswith('/USDT'):
255
- skipped_reason["pair"] += 1
256
- continue
257
 
258
  base_currency = symbol.split('/')[0]
259
-
260
- if any(bad in base_currency for bad in self.BLACKLIST_TOKENS):
261
- skipped_reason["blacklist"] += 1
262
- continue
263
 
264
  vol = ticker.get('quoteVolume')
265
- if vol is None:
266
- vol = ticker.get('info', {}).get('volValue')
267
-
268
  if vol is None: vol = 0.0
269
  else: vol = float(vol)
270
 
271
- if vol < 100_000:
272
- skipped_reason["volume"] += 1
273
- continue
274
 
275
  candidates.append({
276
  'symbol': symbol,
@@ -279,9 +272,6 @@ class DataManager:
279
  'change_24h': float(ticker.get('percentage', 0.0))
280
  })
281
 
282
- print(f" -> 📊 [Debug] Filter Stats: BadPair={skipped_reason['pair']}, Blacklist={skipped_reason['blacklist']}, LowVol={skipped_reason['volume']}")
283
- print(f" -> ✅ [Debug] Candidates Passed: {len(candidates)}")
284
-
285
  candidates.sort(key=lambda x: x['quote_volume'], reverse=True)
286
  return candidates
287
 
 
1
  # ============================================================
2
  # 📂 ml_engine/data_manager.py
3
+ # (V41.4 - GEM-Architect: Dynamic Matrix Scanner)
4
  # ============================================================
5
 
6
  import asyncio
 
28
 
29
  class DataManager:
30
  """
31
+ DataManager V41.4 (Dynamic Matrix Scanner)
32
+ - Fixes KeyError by dynamically finding indicator columns.
33
+ - Prevents crashes from slight library version differences.
34
  """
35
 
36
  def __init__(self, contracts_db, whale_monitor, r2_service=None):
 
52
  'UP', 'DOWN', 'BEAR', 'BULL', '3S', '3L', 'USDD', 'USDP'
53
  ]
54
 
55
+ print(f"📦 [DataManager V41.4] Dynamic Matrix Scanner Online.")
56
 
57
  async def initialize(self):
58
  print(" > [DataManager] Starting initialization...")
 
93
 
94
  print(f"🔍 [L1 Matrix] Regime: {current_regime} | Weights: {scanner_weights}")
95
 
96
+ # 1. جلب العملات (Universe)
97
  all_tickers = await self._fetch_universe_tickers()
98
  if not all_tickers:
99
  print("⚠️ [Layer 1] Universe fetch returned empty.")
100
  return []
101
 
102
+ # 2. الجلب العميق (Deep Fetch)
103
+ top_candidates = all_tickers[:60] # نأخذ عينة جيدة
104
  enriched_data = await self._batch_fetch_ta_data(top_candidates, timeframe='15m', limit=100)
105
 
106
  scored_candidates = []
107
  debug_log_sample = []
108
 
 
 
 
 
 
 
 
109
  for item in enriched_data:
110
  df = item.get('df')
111
  if df is None or len(df) < 50: continue
112
 
113
+ # 🔥 تطبيق الكاشفات
114
  scores = self._apply_scanner_strategies(df, item['symbol'])
115
 
116
  final_score = 0.0
 
121
  final_score += (val['score'] * w)
122
  if val['active']: tags.append(strategy)
123
 
124
+ # Boost بسيط
125
  if item['change_24h'] > 3.0 and current_regime == "BULL": final_score += 10
126
 
127
  item['l1_score'] = final_score
 
154
  ]
155
 
156
  # ==================================================================
157
+ # 🧩 Scanner Strategies Logic (Dynamic Finder)
158
  # ==================================================================
159
  def _apply_scanner_strategies(self, df: pd.DataFrame, symbol: str) -> Dict[str, Any]:
160
+ """تطبيق المؤشرات مع البحث الديناميكي عن أسماء الأعمدة"""
161
  results = {}
162
  try:
163
+ # حماية البيانات
 
164
  df = df.ffill().bfill()
165
  close = df['close']
166
 
167
  # 1. RSI
168
  rsi = ta.rsi(close, length=14)
 
169
  curr_rsi = rsi.iloc[-1] if rsi is not None else 50.0
170
+ if np.isnan(curr_rsi): curr_rsi = 50.0
171
 
172
  score_rsi = 0
173
  active_rsi = False
 
179
  score_rsi = 80
180
  active_rsi = True
181
  elif 30 < curr_rsi <= 50:
182
+ score_rsi = 40
183
 
184
  results["RSI_MOMENTUM"] = {'score': score_rsi, 'active': active_rsi, 'val': curr_rsi}
185
 
186
+ # 2. Bollinger Bands (Dynamic Column Finder)
187
  bb = ta.bbands(close, length=20, std=2)
188
+ score_bb = 0
189
+ active_bb = False
190
+
191
+ if bb is not None and not bb.empty:
192
+ # 🔥 البحث الذكي عن الأعمدة: نبحث عن أي عمود يبدأ بـ BBU (Upper) و BBB (Width)
193
+ # هذا يتجاوز اختلاف الإصدارات (2.0 vs 2)
194
+ bbu_col = next((c for c in bb.columns if c.startswith('BBU')), None)
195
+ bbb_col = next((c for c in bb.columns if c.startswith('BBB')), None)
196
+
197
+ if bbu_col and bbb_col:
198
+ upper = bb[bbu_col].iloc[-1]
199
+ width = bb[bbb_col].iloc[-1]
200
+ curr_price = close.iloc[-1]
201
+
202
+ if curr_price > upper and width > 0.1:
203
+ score_bb = 100
204
+ active_bb = True
205
+ else:
206
+ # إذا فشل البحث، نطبع الأعمدة الموجودة للتشخيص المستقبلي
207
+ # print(f"⚠️ [BB Warning] {symbol} columns mismatch: {bb.columns.tolist()}")
208
+ pass
209
+
210
  results["BB_BREAKOUT"] = {'score': score_bb, 'active': active_bb}
211
 
212
+ # 3. MACD (Dynamic Column Finder)
213
  macd = ta.macd(close)
214
+ score_macd = 0
215
+ active_macd = False
216
+
217
+ if macd is not None and not macd.empty:
218
+ # نبحث عن عمود الهستوجرام (يبدأ بـ MACDh)
219
+ hist_col = next((c for c in macd.columns if c.startswith('MACDh')), None)
220
+
221
+ if hist_col:
222
+ hist = macd[hist_col].iloc[-1]
223
+ if hist > 0:
224
+ score_macd = 100
225
+ active_macd = True
226
+
227
  results["MACD_CROSS"] = {'score': score_macd, 'active': active_macd}
228
 
229
+ # 4. Volume Flow
230
  vol = df['volume']
231
  vol_ma = ta.sma(vol, length=20).iloc[-1]
232
  curr_vol = vol.iloc[-1]
 
238
  results["VOLUME_FLOW"] = {'score': score_vol, 'active': active_vol}
239
 
240
  except Exception as e:
 
241
  print(f"❌ [Scanner Error] {symbol}: {e}")
 
242
  return {k: {'score': 0, 'active': False, 'val': 0} for k in ["RSI_MOMENTUM", "BB_BREAKOUT", "MACD_CROSS", "VOLUME_FLOW"]}
243
 
244
  return results
245
 
246
  # ==================================================================
247
+ # 🌍 Universe & Batch Fetch
248
  # ==================================================================
249
  async def _fetch_universe_tickers(self) -> List[Dict[str, Any]]:
250
+ # print(" -> 📡 [Debug] Contacting Exchange for Tickers...")
251
  try:
252
  tickers = await self.exchange.fetch_tickers()
 
 
253
  candidates = []
 
254
 
255
  for symbol, ticker in tickers.items():
256
+ if not symbol.endswith('/USDT'): continue
 
 
257
 
258
  base_currency = symbol.split('/')[0]
259
+ if any(bad in base_currency for bad in self.BLACKLIST_TOKENS): continue
 
 
 
260
 
261
  vol = ticker.get('quoteVolume')
262
+ if vol is None: vol = ticker.get('info', {}).get('volValue')
 
 
263
  if vol is None: vol = 0.0
264
  else: vol = float(vol)
265
 
266
+ if vol < 100_000: continue
 
 
267
 
268
  candidates.append({
269
  'symbol': symbol,
 
272
  'change_24h': float(ticker.get('percentage', 0.0))
273
  })
274
 
 
 
 
275
  candidates.sort(key=lambda x: x['quote_volume'], reverse=True)
276
  return candidates
277