Riy777 commited on
Commit
fa58d6b
·
verified ·
1 Parent(s): 97b50d2

Update data_manager.py

Browse files
Files changed (1) hide show
  1. data_manager.py +239 -305
data_manager.py CHANGED
@@ -1,4 +1,4 @@
1
- # data_manager.py (Updated to V7.0 - Strategy Qualifier Screening)
2
  import os
3
  import asyncio
4
  import httpx
@@ -11,13 +11,18 @@ import logging
11
  from typing import List, Dict, Any
12
  import pandas as pd
13
 
14
- # 🔴 --- START OF CHANGE (V7.0) --- 🔴
15
- # (إضافة المكتبات اللازمة للغربلة المتقدمة)
16
  try:
17
  import pandas_ta as ta
18
  except ImportError:
19
  print("⚠️ مكتبة pandas_ta غير موجودة، فلتر الغربلة المتقدم سيفشل.")
20
  ta = None
 
 
 
 
 
21
  # 🔴 --- END OF CHANGE --- 🔴
22
 
23
  logging.getLogger("httpx").setLevel(logging.WARNING)
@@ -43,11 +48,18 @@ class DataManager:
43
  self.http_client = None
44
  self.market_cache = {}
45
  self.last_market_load = None
 
 
 
 
 
 
 
46
 
47
  async def initialize(self):
48
  self.http_client = httpx.AsyncClient(timeout=30.0)
49
  await self._load_markets()
50
- print("✅ DataManager initialized - V7.0 (Strategy Qualifier Screening)")
51
 
52
  async def _load_markets(self):
53
  try:
@@ -64,7 +76,6 @@ class DataManager:
64
  print(f"❌ فشل تحميل بيانات الأسواق: {e}")
65
 
66
  async def close(self):
67
- # (إصلاح تسريب الموارد - لا تغيير هنا)
68
  if self.http_client and not self.http_client.is_closed:
69
  await self.http_client.aclose()
70
  print(" ✅ DataManager: http_client closed.")
@@ -78,7 +89,6 @@ class DataManager:
78
 
79
  # (الدوال المساعدة لسياق السوق وأسعار BTC/ETH تبقى كما هي - لا تغيير)
80
  async def get_market_context_async(self):
81
- """جلب سياق السوق الأساسي فقط"""
82
  try:
83
  sentiment_data = await self.get_sentiment_safe_async()
84
  price_data = await self._get_prices_with_fallback()
@@ -103,7 +113,6 @@ class DataManager:
103
  return self._get_minimal_market_context()
104
 
105
  async def get_sentiment_safe_async(self):
106
- """جلب بيانات المشاعر"""
107
  try:
108
  async with httpx.AsyncClient(timeout=10) as client:
109
  response = await client.get("https://api.alternative.me/fng/")
@@ -124,42 +133,26 @@ class DataManager:
124
  return None
125
 
126
  def _determine_market_trend(self, bitcoin_price, sentiment_data):
127
- """تحديد اتجاه السوق"""
128
  if bitcoin_price is None:
129
  return "UNKNOWN"
130
-
131
- score = 0
132
- if bitcoin_price > 60000:
133
- score += 1
134
- elif bitcoin_price < 55000:
135
- score -= 1
136
-
137
  if sentiment_data and sentiment_data.get('feargreed_value') is not None:
138
  fear_greed = sentiment_data.get('feargreed_value')
139
- if fear_greed > 60:
140
- score += 1
141
- elif fear_greed < 40:
142
- score -= 1
143
-
144
- if score >= 1:
145
- return "bull_market"
146
- elif score <= -1:
147
- return "bear_market"
148
- else:
149
- return "sideways_market"
150
 
151
  def _get_btc_sentiment(self, bitcoin_price):
152
- if bitcoin_price is None:
153
- return 'UNKNOWN'
154
- elif bitcoin_price > 60000:
155
- return 'BULLISH'
156
- elif bitcoin_price < 55000:
157
- return 'BEARISH'
158
- else:
159
- return 'NEUTRAL'
160
 
161
  async def _get_prices_with_fallback(self):
162
- """جلب أسعار البيتكوين والإيثيريوم"""
163
  try:
164
  prices = await self._get_prices_from_kucoin_safe()
165
  if prices.get('bitcoin') and prices.get('ethereum'):
@@ -169,63 +162,41 @@ class DataManager:
169
  return {'bitcoin': None, 'ethereum': None}
170
 
171
  async def _get_prices_from_kucoin_safe(self):
172
- if not self.exchange:
173
- return {'bitcoin': None, 'ethereum': None}
174
-
175
  try:
176
  prices = {'bitcoin': None, 'ethereum': None}
177
-
178
  btc_ticker = self.exchange.fetch_ticker('BTC/USDT')
179
  btc_price = float(btc_ticker.get('last', 0)) if btc_ticker.get('last') else None
180
- if btc_price and btc_price > 0:
181
- prices['bitcoin'] = btc_price
182
-
183
  eth_ticker = self.exchange.fetch_ticker('ETH/USDT')
184
  eth_price = float(eth_ticker.get('last', 0)) if eth_ticker.get('last') else None
185
- if eth_price and eth_price > 0:
186
- prices['ethereum'] = eth_price
187
-
188
  return prices
189
-
190
  except Exception as e:
191
- print(f"⚠️ فشل جلب الأسعار من KuCoin: {e}")
192
  return {'bitcoin': None, 'ethereum': None}
193
 
194
  async def _get_prices_from_coingecko(self):
195
- """الاحتياطي: جلب الأسعار من CoinGecko"""
196
  try:
197
  await asyncio.sleep(0.5)
198
  url = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd"
199
-
200
- headers = {
201
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
202
- 'Accept': 'application/json'
203
- }
204
-
205
  async with httpx.AsyncClient(headers=headers) as client:
206
  response = await client.get(url, timeout=10)
207
-
208
  if response.status_code == 429:
209
  await asyncio.sleep(2)
210
  response = await client.get(url, timeout=10)
211
-
212
  response.raise_for_status()
213
  data = response.json()
214
-
215
  btc_price = data.get('bitcoin', {}).get('usd')
216
  eth_price = data.get('ethereum', {}).get('usd')
217
-
218
  if btc_price and eth_price:
219
  return {'bitcoin': btc_price, 'ethereum': eth_price}
220
  else:
221
  return {'bitcoin': None, 'ethereum': None}
222
-
223
  except Exception as e:
224
- print(f"⚠️ فشل جلب الأسعار من CoinGecko: {e}")
225
  return {'bitcoin': None, 'ethereum': None}
226
 
227
  def _get_minimal_market_context(self):
228
- """سياق سوق بدائي عند الفشل"""
229
  return {
230
  'timestamp': datetime.now().isoformat(),
231
  'data_available': False,
@@ -234,20 +205,102 @@ class DataManager:
234
  'data_quality': 'LOW'
235
  }
236
 
237
- # 🔴 --- START OF REFACTOR (V7.0 - Strategy Qualifier) --- 🔴
238
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  async def layer1_rapid_screening(self) -> List[Dict[str, Any]]:
240
  """
241
- الطبقة 1: فحص سريع - (محدث بالكامل V7.0)
242
  1. جلب أفضل 100 عملة حسب الحجم (24 ساعة).
243
  2. جلب 100 شمعة (1H) لهذه الـ 100 عملة.
244
- 3. تطبيق "المؤهل الاستراتيجي" (زخم أو انعكاس).
245
- 4. إرجاع العملات الناجحة فقط للطبقة 2.
 
246
  """
247
- print("📊 الطبقة 1 (V7.0): بدء الغربلة الذكية (المؤهل الاستراتيجي)...")
248
 
249
  # الخطوة 1: جلب أفضل 100 عملة حسب الحجم
250
- volume_data = await self._get_volume_data_optimal() # (أو _get_volume_data_direct_api)
251
  if not volume_data:
252
  volume_data = await self._get_volume_data_direct_api()
253
 
@@ -258,7 +311,7 @@ class DataManager:
258
  volume_data.sort(key=lambda x: x['dollar_volume'], reverse=True)
259
  top_100_by_volume = volume_data[:100]
260
 
261
- print(f"✅ تم تحديد أفضل {len(top_100_by_volume)} عملة حسب الحجم. بدء فلترة 1H...")
262
 
263
  final_candidates = []
264
 
@@ -272,257 +325,184 @@ class DataManager:
272
 
273
  # الخطوة 2: جلب بيانات 1H بالتوازي
274
  tasks = [self._fetch_1h_ohlcv_for_screening(symbol) for symbol in batch_symbols]
275
- results = await asyncio.gather(*tasks, return_exceptions=True)
276
 
277
- # الخطوة 3 و 4: تطبيق المؤهل الاستراتيجي
278
- for j, (result_df) in enumerate(results):
 
 
 
279
  symbol_data = batch_symbols_data[j]
280
  symbol = symbol_data['symbol']
281
 
282
- if isinstance(result_df, Exception) or result_df is None:
283
- # print(f" - {symbol}: فشل جلب 1H")
284
  continue
285
 
286
- # حساب المؤشرات
287
- indicators = self._calculate_screening_indicators(result_df)
288
- if not indicators:
289
- # print(f" - {symbol}: فشل حساب المؤشرات")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  continue
291
 
292
- # تطبيق المؤهل
293
- is_qualified, hint = self._apply_strategy_qualifiers(indicators)
294
 
295
- if is_qualified:
296
- print(f" ✅ {symbol}: نجح (الملمح: {hint})")
297
- # (إضافة البيانات الأولية + التلميح الاستراتيجي)
298
- symbol_data['strategy_hint'] = hint
299
- symbol_data['layer1_score'] = 0.5 # (الدرجة الأولية مجرد علامة نجاح)
300
- symbol_data['reasons_for_candidacy'] = [f'QUALIFIED_{hint.upper()}']
 
 
 
 
301
  final_candidates.append(symbol_data)
302
  # else:
303
- # print(f" - {symbol}: فشل في التأهيل")
304
 
305
- print(f"🎯 اكتملت الغربلة (V7.0). تم تأهيل {len(final_candidates)} عملة من أصل 100 للطبقة 2.")
306
 
307
- # عرض أفضل 15 عملة (إذا كان هناك عدد كافٍ)
308
  print("🏆 المرشحون الناجحون:")
309
  for k, candidate in enumerate(final_candidates[:15]):
310
- hint = candidate.get('strategy_hint', 'N/A')
311
  volume = candidate.get('dollar_volume', 0)
312
- print(f" {k+1:2d}. {candidate['symbol']}: (الملمح: {hint}) | ${volume:,.0f}")
313
 
314
  return final_candidates
315
 
316
- async def _fetch_1h_ohlcv_for_screening(self, symbol: str) -> pd.DataFrame:
317
- """(جديد V7.0) جلب 100 شمعة لإطار الساعة (1H) للغربلة السريعة"""
318
- try:
319
- # 100 شمعة كافية لحساب (RSI 14, EMA 21, MACD 26, BB 20)
320
- ohlcv_data = self.exchange.fetch_ohlcv(symbol, '1h', limit=100)
321
-
322
- if not ohlcv_data or len(ohlcv_data) < 50: # (الحد الأدنى 50)
323
- return None
 
324
 
325
- df = pd.DataFrame(ohlcv_data, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
326
- df[['open', 'high', 'low', 'close', 'volume']] = df[['open', 'high', 'low', 'close', 'volume']].astype(float)
327
- return df
328
- except Exception:
329
- return None
330
 
331
- def _calculate_screening_indicators(self, df: pd.DataFrame) -> Dict[str, Any]:
332
- """(جديد V7.0) حساب المؤشرات المحددة للغربلة من بيانات 1H"""
333
- if ta is None or df is None or len(df) < 30: # (الحد الأدنى 30)
334
- return None
335
- try:
336
- indicators = {}
337
- close = df['close']
338
-
339
- # حساب المؤشرات
340
- indicators['rsi'] = ta.rsi(close, length=14).iloc[-1]
341
- indicators['ema_9'] = ta.ema(close, length=9).iloc[-1]
342
- indicators['ema_21'] = ta.ema(close, length=21).iloc[-1]
343
-
344
- macd = ta.macd(close, fast=12, slow=26, signal=9)
345
- indicators['macd_hist'] = macd['MACDh_12_26_9'].iloc[-1]
346
-
347
- indicators['atr'] = ta.atr(df['high'], df['low'], close, length=14).iloc[-1]
348
-
349
- bbands = ta.bbands(close, length=20, std=2)
350
- indicators['bb_lower'] = bbands['BBL_20_2.0'].iloc[-1]
351
 
352
- indicators['current_price'] = close.iloc[-1]
353
 
354
- # التأكد من عدم وجود قيم NaN (فارغة)
355
- if any(pd.isna(v) for v in indicators.values()):
356
- return None
357
-
358
- return indicators
359
- except Exception:
360
- return None
361
 
362
- def _apply_strategy_qualifiers(self, indicators: Dict[str, Any]) -> (bool, str):
363
- """(جديد V7.0) تطبيق ملامح التأهيل (زخم أو انعكاس)"""
364
- if not indicators:
365
- return (False, None)
366
-
367
  try:
368
- current_price = indicators['current_price']
369
-
370
- # --- 1. ملمح الزخم (Momentum Profile) ---
371
- # (نبحث عن قوة واتجاه صاعد وتسارع)
372
- is_momentum = (
373
- indicators['ema_9'] > indicators['ema_21'] and
374
- indicators['rsi'] > 50 and # (في النصف القوي)
375
- indicators['macd_hist'] > 0 and # (تسارع إيجابي)
376
- indicators['atr'] > (current_price * 0.005) # (تقلب معقول، ليس عملة ميتة)
377
- )
378
- if is_momentum:
379
- return (True, 'momentum')
380
-
381
- # --- 2. ملمح الانعكاس (Reversion Profile) ---
382
- # (نبحث عن بيع مبالغ فيه عند منطقة دعم محتملة)
383
- is_reversion = (
384
- indicators['rsi'] < 35 and # (ذروة بيع واضحة)
385
- current_price < indicators['ema_21'] and # (بعيد عن المتوسط)
386
- current_price <= (indicators['bb_lower'] * 1.005) # (عند أو قرب الحد السفلي لبولينجر)
387
- )
388
- if is_reversion:
389
- return (True, 'reversion')
390
-
391
- # --- 3. فشل في كلاهما ---
392
- return (False, None)
393
 
 
 
 
394
  except Exception:
395
- return (False, None)
396
 
397
- # 🔴 --- END OF REFACTOR (V7.0) --- 🔴
398
 
399
 
400
  # (دوال جلب الحجم تبقى كما هي لأنها فعالة)
401
  async def _get_volume_data_optimal(self) -> List[Dict[str, Any]]:
402
- """الطريقة المثلى: استخدام fetch_tickers لجميع البيانات مرة واحدة"""
403
  try:
404
- if not self.exchange:
405
- return []
406
-
407
  tickers = self.exchange.fetch_tickers()
408
-
409
  volume_data = []
410
- processed = 0
411
-
412
  for symbol, ticker in tickers.items():
413
- if not symbol.endswith('/USDT') or not ticker.get('active', True):
414
- continue
415
-
416
  current_price = ticker.get('last', 0)
417
  quote_volume = ticker.get('quoteVolume', 0)
418
-
419
- if current_price is None or current_price <= 0:
420
- continue
421
-
422
  if quote_volume is not None and quote_volume > 0:
423
  dollar_volume = quote_volume
424
  else:
425
  base_volume = ticker.get('baseVolume', 0)
426
- if base_volume is None:
427
- continue
428
  dollar_volume = base_volume * current_price
429
-
430
- if dollar_volume is None or dollar_volume < 50000:
431
- continue
432
-
433
  price_change_24h = (ticker.get('percentage', 0) or 0) * 100
434
- if price_change_24h is None:
435
- price_change_24h = 0
436
-
437
  volume_data.append({
438
- 'symbol': symbol,
439
- 'dollar_volume': dollar_volume,
440
- 'current_price': current_price,
441
- 'volume_24h': ticker.get('baseVolume', 0) or 0,
442
  'price_change_24h': price_change_24h
443
  })
444
-
445
- processed += 1
446
-
447
- print(f"✅ تم معالجة {processed} عملة في الطريقة المثلى (لجلب الحجم)")
448
  return volume_data
449
-
450
  except Exception as e:
451
  print(f"❌ خطأ في جلب بيانات الحجم المثلى: {e}")
452
- traceback.print_exc()
453
  return []
454
 
455
  async def _get_volume_data_direct_api(self) -> List[Dict[str, Any]]:
456
- """الطريقة الثانية: استخدام KuCoin API مباشرة (احتياطي)"""
457
  try:
458
  url = "https://api.kucoin.com/api/v1/market/allTickers"
459
-
460
  async with httpx.AsyncClient(timeout=15) as client:
461
  response = await client.get(url)
462
  response.raise_for_status()
463
  data = response.json()
464
-
465
- if data.get('code') != '200000':
466
- raise ValueError(f"استجابة API غير متوقعة: {data.get('code')}")
467
-
468
  tickers = data['data']['ticker']
469
  volume_data = []
470
-
471
  for ticker in tickers:
472
  symbol = ticker['symbol']
473
-
474
- if not symbol.endswith('USDT'):
475
- continue
476
-
477
  formatted_symbol = symbol.replace('-', '/')
478
-
479
  try:
480
  vol_value = ticker.get('volValue')
481
  last_price = ticker.get('last')
482
  change_rate = ticker.get('changeRate')
483
  vol = ticker.get('vol')
484
-
485
- if vol_value is None or last_price is None or change_rate is None or vol is None:
486
- continue
487
-
488
  dollar_volume = float(vol_value) if vol_value else 0
489
  current_price = float(last_price) if last_price else 0
490
  price_change = (float(change_rate) * 100) if change_rate else 0
491
  volume_24h = float(vol) if vol else 0
492
-
493
  if dollar_volume >= 50000 and current_price > 0:
494
  volume_data.append({
495
- 'symbol': formatted_symbol,
496
- 'dollar_volume': dollar_volume,
497
- 'current_price': current_price,
498
- 'volume_24h': volume_24h,
499
  'price_change_24h': price_change
500
  })
501
- except (ValueError, TypeError, KeyError) as e:
502
- continue
503
-
504
  print(f"✅ تم معالجة {len(volume_data)} عملة في الطريقة المباشرة (لجلب الحجم)")
505
  return volume_data
506
-
507
  except Exception as e:
508
  print(f"❌ خطأ في جلب بيانات الحجم المباشر: {e}")
509
- traceback.print_exc()
510
  return []
511
 
512
- # 🔴 --- START OF DELETION (V7.0) --- 🔴
513
- # (تم حذف الدوال التالية لأنها لم تعد مستخدمة في V7.0)
514
- # _get_volume_data_traditional
515
- # _process_single_symbol
516
- # _apply_advanced_indicators
517
- # _get_detailed_symbol_data
518
- # _calculate_advanced_score
519
- # _calculate_volume_score
520
- # _calculate_volatility
521
- # _calculate_volatility_score (تم نقل منطق فلتر العملة المستقرة إلى _apply_strategy_qualifiers)
522
- # _calculate_price_strength
523
- # _calculate_momentum (تم نقل منطق الزخم إلى _apply_strategy_qualifiers)
524
- # 🔴 --- END OF DELETION --- 🔴
525
-
526
  # (دالة تدفق الشموع تبقى كما هي - لا تغيير)
527
  async def stream_ohlcv_data(self, symbols: List[str], queue: asyncio.Queue):
528
  """
@@ -552,6 +532,9 @@ class DataManager:
552
  if isinstance(result, Exception):
553
  print(f" ❌ [المنتج] فشل جلب {symbol}: {result}")
554
  elif result is not None:
 
 
 
555
  successful_data_for_batch.append(result)
556
  successful_count += 1
557
  timeframes_count = result.get('successful_timeframes', 0)
@@ -587,12 +570,8 @@ class DataManager:
587
  ohlcv_data = {}
588
 
589
  timeframes = [
590
- ('5m', 200),
591
- ('15m', 200),
592
- ('1h', 200),
593
- ('4h', 200),
594
- ('1d', 200),
595
- ('1w', 200),
596
  ]
597
 
598
  timeframe_tasks = []
@@ -607,10 +586,7 @@ class DataManager:
607
 
608
  for i, (timeframe, limit) in enumerate(timeframes):
609
  result = timeframe_results[i]
610
-
611
- if isinstance(result, Exception):
612
- continue
613
-
614
  if result and len(result) >= 10:
615
  ohlcv_data[timeframe] = result
616
  successful_timeframes += 1
@@ -618,52 +594,36 @@ class DataManager:
618
  if successful_timeframes >= min_required_timeframes and ohlcv_data:
619
  try:
620
  current_price = await self.get_latest_price_async(symbol)
621
-
622
  if current_price is None:
623
  for timeframe_data in ohlcv_data.values():
624
  if timeframe_data and len(timeframe_data) > 0:
625
  last_candle = timeframe_data[-1]
626
  if len(last_candle) >= 5:
627
- current_price = last_candle[4]
628
- break
629
-
630
- if current_price is None:
631
- return None
632
 
633
  result_data = {
634
- 'symbol': symbol,
635
- 'ohlcv': ohlcv_data,
636
- 'raw_ohlcv': ohlcv_data,
637
- 'current_price': current_price,
638
- 'timestamp': datetime.now().isoformat(),
639
  'candles_count': {tf: len(data) for tf, data in ohlcv_data.items()},
640
  'successful_timeframes': successful_timeframes
641
  }
642
-
643
  return result_data
644
-
645
- except Exception as price_error:
646
- return None
647
- else:
648
- return None
649
-
650
- except Exception as e:
651
- return None
652
 
653
  async def _fetch_single_timeframe_improved(self, symbol: str, timeframe: str, limit: int):
654
  """جلب بيانات إطار زمني واحد مع تحسين التعامل مع الأخطاء"""
655
  max_retries = 3
656
  retry_delay = 2
657
-
658
  for attempt in range(max_retries):
659
  try:
660
  ohlcv_data = self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
661
-
662
  if ohlcv_data and len(ohlcv_data) > 0:
663
  return ohlcv_data
664
  else:
665
  return []
666
-
667
  except Exception as e:
668
  if attempt < max_retries - 1:
669
  await asyncio.sleep(retry_delay * (attempt + 1))
@@ -673,57 +633,31 @@ class DataManager:
673
  async def get_latest_price_async(self, symbol):
674
  """جلب السعر الحالي لعملة محددة"""
675
  try:
676
- if not self.exchange:
677
- return None
678
-
679
- if not symbol or '/' not in symbol:
680
- return None
681
-
682
  ticker = self.exchange.fetch_ticker(symbol)
683
-
684
- if not ticker:
685
- return None
686
-
687
  current_price = ticker.get('last')
688
-
689
- if current_price is None:
690
- return None
691
-
692
  return float(current_price)
693
-
694
- except Exception as e:
695
- return None
696
 
697
  # (دوال دعم بيانات الحيتان تبقى كما هي - لا تغيير)
698
  async def get_whale_data_for_symbol(self, symbol):
699
- """جلب بيانات الحيتان لعملة محددة"""
700
  try:
701
  if self.whale_monitor:
702
  whale_data = await self.whale_monitor.get_symbol_whale_activity(symbol)
703
  return whale_data
704
- else:
705
- return None
706
- except Exception as e:
707
- return None
708
 
709
  async def get_whale_trading_signal(self, symbol, whale_data, market_context):
710
- """جلب إشارة التداول بناءً على بيانات الحيتان"""
711
  try:
712
  if self.whale_monitor:
713
  return await self.whale_monitor.generate_whale_trading_signal(symbol, whale_data, market_context)
714
  else:
715
- return {
716
- 'action': 'HOLD',
717
- 'confidence': 0.3,
718
- 'reason': 'Whale monitor not available',
719
- 'source': 'whale_analysis'
720
- }
721
  except Exception as e:
722
- return {
723
- 'action': 'HOLD',
724
- 'confidence': 0.3,
725
- 'reason': f'Error: {str(e)}',
726
- 'source': 'whale_analysis'
727
- }
728
 
729
- print("✅ DataManager loaded - V7.0 (Strategy Qualifier Screening)")
 
1
+ # data_manager.py (Updated to V7.1 - 1H Detector Screening)
2
  import os
3
  import asyncio
4
  import httpx
 
11
  from typing import List, Dict, Any
12
  import pandas as pd
13
 
14
+ # 🔴 --- START OF CHANGE (V7.1) --- 🔴
15
+ # (استيراد الأدوات اللازمة لـ "الكاشف المصغر")
16
  try:
17
  import pandas_ta as ta
18
  except ImportError:
19
  print("⚠️ مكتبة pandas_ta غير موجودة، فلتر الغربلة المتقدم سيفشل.")
20
  ta = None
21
+
22
+ # (استيراد الوحدات الأساسية من محرك ML لتشغيل الكاشف المصغر)
23
+ from ml_engine.indicators import AdvancedTechnicalAnalyzer
24
+ from ml_engine.monte_carlo import MonteCarloAnalyzer
25
+ from ml_engine.patterns import ChartPatternAnalyzer
26
  # 🔴 --- END OF CHANGE --- 🔴
27
 
28
  logging.getLogger("httpx").setLevel(logging.WARNING)
 
48
  self.http_client = None
49
  self.market_cache = {}
50
  self.last_market_load = None
51
+
52
+ # 🔴 --- START OF CHANGE (V7.1) --- 🔴
53
+ # (تهيئة أدوات الكاشف المصغر)
54
+ self.technical_analyzer = AdvancedTechnicalAnalyzer()
55
+ self.monte_carlo_analyzer = MonteCarloAnalyzer()
56
+ self.pattern_analyzer = ChartPatternAnalyzer()
57
+ # 🔴 --- END OF CHANGE --- 🔴
58
 
59
  async def initialize(self):
60
  self.http_client = httpx.AsyncClient(timeout=30.0)
61
  await self._load_markets()
62
+ print("✅ DataManager initialized - V7.1 (1H Detector Screening)")
63
 
64
  async def _load_markets(self):
65
  try:
 
76
  print(f"❌ فشل تحميل بيانات الأسواق: {e}")
77
 
78
  async def close(self):
 
79
  if self.http_client and not self.http_client.is_closed:
80
  await self.http_client.aclose()
81
  print(" ✅ DataManager: http_client closed.")
 
89
 
90
  # (الدوال المساعدة لسياق السوق وأسعار BTC/ETH تبقى كما هي - لا تغيير)
91
  async def get_market_context_async(self):
 
92
  try:
93
  sentiment_data = await self.get_sentiment_safe_async()
94
  price_data = await self._get_prices_with_fallback()
 
113
  return self._get_minimal_market_context()
114
 
115
  async def get_sentiment_safe_async(self):
 
116
  try:
117
  async with httpx.AsyncClient(timeout=10) as client:
118
  response = await client.get("https://api.alternative.me/fng/")
 
133
  return None
134
 
135
  def _determine_market_trend(self, bitcoin_price, sentiment_data):
 
136
  if bitcoin_price is None:
137
  return "UNKNOWN"
138
+ if bitcoin_price > 60000: score = 1
139
+ elif bitcoin_price < 55000: score = -1
140
+ else: score = 0
 
 
 
 
141
  if sentiment_data and sentiment_data.get('feargreed_value') is not None:
142
  fear_greed = sentiment_data.get('feargreed_value')
143
+ if fear_greed > 60: score += 1
144
+ elif fear_greed < 40: score -= 1
145
+ if score >= 1: return "bull_market"
146
+ elif score <= -1: return "bear_market"
147
+ else: return "sideways_market"
 
 
 
 
 
 
148
 
149
  def _get_btc_sentiment(self, bitcoin_price):
150
+ if bitcoin_price is None: return 'UNKNOWN'
151
+ elif bitcoin_price > 60000: return 'BULLISH'
152
+ elif bitcoin_price < 55000: return 'BEARISH'
153
+ else: return 'NEUTRAL'
 
 
 
 
154
 
155
  async def _get_prices_with_fallback(self):
 
156
  try:
157
  prices = await self._get_prices_from_kucoin_safe()
158
  if prices.get('bitcoin') and prices.get('ethereum'):
 
162
  return {'bitcoin': None, 'ethereum': None}
163
 
164
  async def _get_prices_from_kucoin_safe(self):
165
+ if not self.exchange: return {'bitcoin': None, 'ethereum': None}
 
 
166
  try:
167
  prices = {'bitcoin': None, 'ethereum': None}
 
168
  btc_ticker = self.exchange.fetch_ticker('BTC/USDT')
169
  btc_price = float(btc_ticker.get('last', 0)) if btc_ticker.get('last') else None
170
+ if btc_price and btc_price > 0: prices['bitcoin'] = btc_price
 
 
171
  eth_ticker = self.exchange.fetch_ticker('ETH/USDT')
172
  eth_price = float(eth_ticker.get('last', 0)) if eth_ticker.get('last') else None
173
+ if eth_price and eth_price > 0: prices['ethereum'] = eth_price
 
 
174
  return prices
 
175
  except Exception as e:
 
176
  return {'bitcoin': None, 'ethereum': None}
177
 
178
  async def _get_prices_from_coingecko(self):
 
179
  try:
180
  await asyncio.sleep(0.5)
181
  url = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd"
182
+ headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'application/json'}
 
 
 
 
 
183
  async with httpx.AsyncClient(headers=headers) as client:
184
  response = await client.get(url, timeout=10)
 
185
  if response.status_code == 429:
186
  await asyncio.sleep(2)
187
  response = await client.get(url, timeout=10)
 
188
  response.raise_for_status()
189
  data = response.json()
 
190
  btc_price = data.get('bitcoin', {}).get('usd')
191
  eth_price = data.get('ethereum', {}).get('usd')
 
192
  if btc_price and eth_price:
193
  return {'bitcoin': btc_price, 'ethereum': eth_price}
194
  else:
195
  return {'bitcoin': None, 'ethereum': None}
 
196
  except Exception as e:
 
197
  return {'bitcoin': None, 'ethereum': None}
198
 
199
  def _get_minimal_market_context(self):
 
200
  return {
201
  'timestamp': datetime.now().isoformat(),
202
  'data_available': False,
 
205
  'data_quality': 'LOW'
206
  }
207
 
208
+ # 🔴 --- START OF REFACTOR (V7.1 - 1H Detector Screening) --- 🔴
209
 
210
+ def _create_dataframe(self, candles: List) -> pd.DataFrame:
211
+ """(جديد V7.1) دالة مساعدة لإنشاء DataFrame لتحليل 1H"""
212
+ try:
213
+ if not candles: return pd.DataFrame()
214
+ df = pd.DataFrame(candles, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
215
+ df[['open', 'high', 'low', 'close', 'volume']] = df[['open', 'high', 'low', 'close', 'volume']].astype(float)
216
+ df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
217
+ df.set_index('timestamp', inplace=True)
218
+ df.sort_index(inplace=True)
219
+ return df
220
+ except Exception as e:
221
+ print(f"❌ خطأ في إنشاء DataFrame لمرشح 1H: {e}")
222
+ return pd.DataFrame()
223
+
224
+ def _calculate_1h_filter_score(self, analysis: Dict) -> float:
225
+ """
226
+ (جديد V7.1) - "الكاشف المصغر"
227
+ مقتبس من processor.py لحساب درجة (0-1) بناءً على بيانات 1H فقط.
228
+ يستخدم (المؤشرات، مونت كارلو، الأنماط) فقط.
229
+ """
230
+ try:
231
+ # 1. درجة الأنماط (Pattern Score)
232
+ pattern_confidence = analysis.get('pattern_analysis', {}).get('pattern_confidence', 0)
233
+
234
+ # 2. درجة مونت كارلو (Monte Carlo Score)
235
+ mc_distribution = analysis.get('monte_carlo_distribution')
236
+ monte_carlo_score = 0
237
+
238
+ if mc_distribution:
239
+ prob_gain = mc_distribution.get('probability_of_gain', 0)
240
+ var_95_value = mc_distribution.get('risk_metrics', {}).get('VaR_95_value', 0)
241
+ current_price = analysis.get('current_price', 1)
242
+
243
+ if current_price > 0:
244
+ normalized_var = var_95_value / current_price
245
+ risk_penalty = 1.0
246
+ if normalized_var > 0.05: risk_penalty = 0.5
247
+ elif normalized_var > 0.03: risk_penalty = 0.8
248
+
249
+ # (تطبيع الاحتمالية: 0.5 -> 0, 1.0 -> 1.0)
250
+ normalized_prob_score = max(0.0, (prob_gain - 0.5) * 2)
251
+ monte_carlo_score = normalized_prob_score * risk_penalty
252
+
253
+ # 3. درجة المؤشرات (Indicator Score)
254
+ # (تقييم بسيط للمؤشرات الرئيسية لـ 1H)
255
+ indicator_score = 0
256
+ indicators = analysis.get('advanced_indicators', {}).get('1h', {})
257
+ if indicators:
258
+ rsi = indicators.get('rsi', 50)
259
+ macd_hist = indicators.get('macd_hist', 0)
260
+ ema_9 = indicators.get('ema_9', 0)
261
+ ema_21 = indicators.get('ema_21', 0)
262
+
263
+ # ملمح الزخم
264
+ if rsi > 55 and macd_hist > 0 and ema_9 > ema_21:
265
+ indicator_score = min(0.5 + (rsi - 55) / 50 + (macd_hist / (analysis.get('current_price', 1) * 0.001)), 1.0)
266
+ # ملمح الانعكاس
267
+ elif rsi < 35:
268
+ indicator_score = min(0.4 + (35 - rsi) / 35, 0.8)
269
+
270
+ # 4. حساب النتيجة النهائية (بدون استراتيجيات أو حيتان)
271
+ # الأوزان: مونت كارلو (40%)، الأنماط (30%)، المؤشرات (30%)
272
+ components = []
273
+ weights = []
274
+
275
+ if monte_carlo_score > 0: components.append(monte_carlo_score); weights.append(0.40)
276
+ if pattern_confidence > 0: components.append(pattern_confidence); weights.append(0.30)
277
+ if indicator_score > 0: components.append(indicator_score); weights.append(0.30)
278
+
279
+ if not components: return 0
280
+ total_weight = sum(weights)
281
+ if total_weight == 0: return 0
282
+
283
+ enhanced_score = sum(comp * weight for comp, weight in zip(components, weights)) / total_weight
284
+ return min(max(enhanced_score, 0.0), 1.0)
285
+
286
+ except Exception as e:
287
+ print(f"❌ خطأ في حساب درجة فلتر 1H: {e}")
288
+ return 0.0
289
+
290
+
291
  async def layer1_rapid_screening(self) -> List[Dict[str, Any]]:
292
  """
293
+ الطبقة 1: فحص سريع - (محدث بالكامل V7.1)
294
  1. جلب أفضل 100 عملة حسب الحجم (24 ساعة).
295
  2. جلب 100 شمعة (1H) لهذه الـ 100 عملة.
296
+ 3. تشغيل "الكاشف المصغر" (مؤشرات، مونت كارلو، أنماط) على 1H.
297
+ 4. استبعاد أي عملة درجتها < 0.20.
298
+ 5. إرجاع العملات الناجحة فقط للطبقة 2.
299
  """
300
+ print("📊 الطبقة 1 (V7.1): بدء الغربلة (الكاشف المصغر 1H)...")
301
 
302
  # الخطوة 1: جلب أفضل 100 عملة حسب الحجم
303
+ volume_data = await self._get_volume_data_optimal()
304
  if not volume_data:
305
  volume_data = await self._get_volume_data_direct_api()
306
 
 
311
  volume_data.sort(key=lambda x: x['dollar_volume'], reverse=True)
312
  top_100_by_volume = volume_data[:100]
313
 
314
+ print(f"✅ تم تحديد أفضل {len(top_100_by_volume)} عملة. بدء تشغيل الكاشف المصغر (1H)...")
315
 
316
  final_candidates = []
317
 
 
325
 
326
  # الخطوة 2: جلب بيانات 1H بالتوازي
327
  tasks = [self._fetch_1h_ohlcv_for_screening(symbol) for symbol in batch_symbols]
328
+ results_candles = await asyncio.gather(*tasks, return_exceptions=True)
329
 
330
+ # الخطوة 3 و 4: تطبيق الكاشف المصغر
331
+ analysis_tasks = []
332
+ valid_symbol_data = []
333
+
334
+ for j, (candles) in enumerate(results_candles):
335
  symbol_data = batch_symbols_data[j]
336
  symbol = symbol_data['symbol']
337
 
338
+ if isinstance(candles, Exception) or not candles or len(candles) < 50:
 
339
  continue
340
 
341
+ # (إعداد البيانات للمحللات)
342
+ ohlcv_1h_only = {'1h': candles}
343
+ symbol_data['ohlcv_1h'] = ohlcv_1h_only # (تخزين مؤقت)
344
+ symbol_data['current_price'] = candles[-1][4] # آخر سعر إغلاق
345
+ analysis_tasks.append(self._run_mini_detector(symbol_data))
346
+ valid_symbol_data.append(symbol_data)
347
+
348
+ if not analysis_tasks:
349
+ continue
350
+
351
+ # تشغيل التحليلات (MC, Patterns, Indicators) بالتوازي
352
+ analysis_results = await asyncio.gather(*analysis_tasks, return_exceptions=True)
353
+
354
+ for j, (analysis_output) in enumerate(analysis_results):
355
+ symbol_data = valid_symbol_data[j] # (استرجاع بيانات الرمز المطابقة)
356
+ symbol = symbol_data['symbol']
357
+
358
+ if isinstance(analysis_output, Exception):
359
+ print(f" - {symbol}: فشل الكاشف المصغر ({analysis_output})")
360
  continue
361
 
362
+ # الخطوة 4: حساب النتيجة النهائية للفلتر
363
+ filter_score = self._calculate_1h_filter_score(analysis_output)
364
 
365
+ # الخطوة 5: تطبيق العتبة (Threshold)
366
+ if filter_score >= 0.20:
367
+ print(f" ✅ {symbol}: نجح (الدرجة: {filter_score:.2f})")
368
+ # (إضافة البيانات الأولية للطبقة 2)
369
+ symbol_data['layer1_score'] = filter_score
370
+ symbol_data['reasons_for_candidacy'] = [f'1H_DETECTOR_PASS']
371
+
372
+ # (تنظيف البيانات قبل إرسالها)
373
+ if 'ohlcv_1h' in symbol_data: del symbol_data['ohlcv_1h']
374
+
375
  final_candidates.append(symbol_data)
376
  # else:
377
+ # print(f" - {symbol}: فشل (الدرجة: {filter_score:.2f})")
378
 
379
+ print(f"🎯 اكتملت الغربلة (V7.1). تم تأهيل {len(final_candidates)} عملة من أصل 100 للطبقة 2.")
380
 
 
381
  print("🏆 المرشحون الناجحون:")
382
  for k, candidate in enumerate(final_candidates[:15]):
383
+ score = candidate.get('layer1_score', 0)
384
  volume = candidate.get('dollar_volume', 0)
385
+ print(f" {k+1:2d}. {candidate['symbol']}: (الدرجة: {score:.2f}) | ${volume:,.0f}")
386
 
387
  return final_candidates
388
 
389
+ async def _run_mini_detector(self, symbol_data: Dict) -> Dict:
390
+ """(جديد V7.1) يشغل المحللات الأساسية بالتوازي على بيانات 1H فقط"""
391
+ ohlcv_1h = symbol_data.get('ohlcv_1h')
392
+ current_price = symbol_data.get('current_price')
393
+
394
+ # (إنشاء DataFrame مرة واحدة)
395
+ df = self._create_dataframe(ohlcv_1h.get('1h'))
396
+ if df.empty:
397
+ raise ValueError("DataFrame فارغ لتحليل 1H")
398
 
399
+ analysis_dict = {'current_price': current_price}
 
 
 
 
400
 
401
+ # إنشاء المهام
402
+ # (لا حاجة لـ asyncio.create_task لأن هذه الدوال ليست async)
403
+ # (ملاحظة: جعلنا دوال التحليل async في ملفاتها لتكون متوافقة)
404
+
405
+ task_indicators = self.technical_analyzer.calculate_all_indicators(df, '1h')
406
+ task_mc = self.monte_carlo_analyzer.generate_1h_price_distribution(ohlcv_1h)
407
+ task_pattern = self.pattern_analyzer.detect_chart_patterns(ohlcv_1h)
408
+
409
+ # (تشغيل المهام التي هي async)
410
+ results = await asyncio.gather(task_mc, task_pattern, return_exceptions=True)
411
+
412
+ # (معالجة النتائج)
413
+ analysis_dict['advanced_indicators'] = {'1h': task_indicators} # (هذه دالة متزامنة)
414
+
415
+ if not isinstance(results[0], Exception):
416
+ analysis_dict['monte_carlo_distribution'] = results[0]
417
+ if not isinstance(results[1], Exception):
418
+ analysis_dict['pattern_analysis'] = results[1]
 
 
419
 
420
+ return analysis_dict
421
 
 
 
 
 
 
 
 
422
 
423
+ async def _fetch_1h_ohlcv_for_screening(self, symbol: str) -> List:
424
+ """(جديد V7.1) جلب 100 شمعة لإطار الساعة (1H) للغربلة السريعة"""
 
 
 
425
  try:
426
+ # 100 شمعة كافية لحساب (RSI 14, EMA 21, MACD 26, BB 20)
427
+ ohlcv_data = self.exchange.fetch_ohlcv(symbol, '1h', limit=100)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
428
 
429
+ if not ohlcv_data or len(ohlcv_data) < 50: # (الحد الأدنى 50)
430
+ return None
431
+ return ohlcv_data
432
  except Exception:
433
+ return None
434
 
435
+ # 🔴 --- END OF REFACTOR (V7.1) --- 🔴
436
 
437
 
438
  # (دوال جلب الحجم تبقى كما هي لأنها فعالة)
439
  async def _get_volume_data_optimal(self) -> List[Dict[str, Any]]:
 
440
  try:
441
+ if not self.exchange: return []
 
 
442
  tickers = self.exchange.fetch_tickers()
 
443
  volume_data = []
 
 
444
  for symbol, ticker in tickers.items():
445
+ if not symbol.endswith('/USDT') or not ticker.get('active', True): continue
 
 
446
  current_price = ticker.get('last', 0)
447
  quote_volume = ticker.get('quoteVolume', 0)
448
+ if current_price is None or current_price <= 0: continue
 
 
 
449
  if quote_volume is not None and quote_volume > 0:
450
  dollar_volume = quote_volume
451
  else:
452
  base_volume = ticker.get('baseVolume', 0)
453
+ if base_volume is None: continue
 
454
  dollar_volume = base_volume * current_price
455
+ if dollar_volume is None or dollar_volume < 50000: continue
 
 
 
456
  price_change_24h = (ticker.get('percentage', 0) or 0) * 100
457
+ if price_change_24h is None: price_change_24h = 0
 
 
458
  volume_data.append({
459
+ 'symbol': symbol, 'dollar_volume': dollar_volume,
460
+ 'current_price': current_price, 'volume_24h': ticker.get('baseVolume', 0) or 0,
 
 
461
  'price_change_24h': price_change_24h
462
  })
463
+ print(f"✅ تم معالجة {len(volume_data)} عملة في الطريقة المثلى (لجلب الحجم)")
 
 
 
464
  return volume_data
 
465
  except Exception as e:
466
  print(f"❌ خطأ في جلب بيانات الحجم المثلى: {e}")
 
467
  return []
468
 
469
  async def _get_volume_data_direct_api(self) -> List[Dict[str, Any]]:
 
470
  try:
471
  url = "https://api.kucoin.com/api/v1/market/allTickers"
 
472
  async with httpx.AsyncClient(timeout=15) as client:
473
  response = await client.get(url)
474
  response.raise_for_status()
475
  data = response.json()
476
+ if data.get('code') != '200000': raise ValueError(f"استجابة API غير متوقعة: {data.get('code')}")
 
 
 
477
  tickers = data['data']['ticker']
478
  volume_data = []
 
479
  for ticker in tickers:
480
  symbol = ticker['symbol']
481
+ if not symbol.endswith('USDT'): continue
 
 
 
482
  formatted_symbol = symbol.replace('-', '/')
 
483
  try:
484
  vol_value = ticker.get('volValue')
485
  last_price = ticker.get('last')
486
  change_rate = ticker.get('changeRate')
487
  vol = ticker.get('vol')
488
+ if vol_value is None or last_price is None or change_rate is None or vol is None: continue
 
 
 
489
  dollar_volume = float(vol_value) if vol_value else 0
490
  current_price = float(last_price) if last_price else 0
491
  price_change = (float(change_rate) * 100) if change_rate else 0
492
  volume_24h = float(vol) if vol else 0
 
493
  if dollar_volume >= 50000 and current_price > 0:
494
  volume_data.append({
495
+ 'symbol': formatted_symbol, 'dollar_volume': dollar_volume,
496
+ 'current_price': current_price, 'volume_24h': volume_24h,
 
 
497
  'price_change_24h': price_change
498
  })
499
+ except (ValueError, TypeError, KeyError) as e: continue
 
 
500
  print(f"✅ تم معالجة {len(volume_data)} عملة في الطريقة المباشرة (لجلب الحجم)")
501
  return volume_data
 
502
  except Exception as e:
503
  print(f"❌ خطأ في جلب بيانات الحجم المباشر: {e}")
 
504
  return []
505
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
506
  # (دالة تدفق الشموع تبقى كما هي - لا تغيير)
507
  async def stream_ohlcv_data(self, symbols: List[str], queue: asyncio.Queue):
508
  """
 
532
  if isinstance(result, Exception):
533
  print(f" ❌ [المنتج] فشل جلب {symbol}: {result}")
534
  elif result is not None:
535
+ # (مهم: نمرر بيانات المرشح الأولية التي تحتوي على الدرجة)
536
+ original_data = next((s for s in symbols if s['symbol'] == symbol), {'symbol': symbol})
537
+ result.update(original_data) # (دمج الدرجة الأولية مع البيانات الكاملة)
538
  successful_data_for_batch.append(result)
539
  successful_count += 1
540
  timeframes_count = result.get('successful_timeframes', 0)
 
570
  ohlcv_data = {}
571
 
572
  timeframes = [
573
+ ('5m', 200), ('15m', 200), ('1h', 200),
574
+ ('4h', 200), ('1d', 200), ('1w', 200),
 
 
 
 
575
  ]
576
 
577
  timeframe_tasks = []
 
586
 
587
  for i, (timeframe, limit) in enumerate(timeframes):
588
  result = timeframe_results[i]
589
+ if isinstance(result, Exception): continue
 
 
 
590
  if result and len(result) >= 10:
591
  ohlcv_data[timeframe] = result
592
  successful_timeframes += 1
 
594
  if successful_timeframes >= min_required_timeframes and ohlcv_data:
595
  try:
596
  current_price = await self.get_latest_price_async(symbol)
 
597
  if current_price is None:
598
  for timeframe_data in ohlcv_data.values():
599
  if timeframe_data and len(timeframe_data) > 0:
600
  last_candle = timeframe_data[-1]
601
  if len(last_candle) >= 5:
602
+ current_price = last_candle[4]; break
603
+ if current_price is None: return None
 
 
 
604
 
605
  result_data = {
606
+ 'symbol': symbol, 'ohlcv': ohlcv_data, 'raw_ohlcv': ohlcv_data,
607
+ 'current_price': current_price, 'timestamp': datetime.now().isoformat(),
 
 
 
608
  'candles_count': {tf: len(data) for tf, data in ohlcv_data.items()},
609
  'successful_timeframes': successful_timeframes
610
  }
 
611
  return result_data
612
+ except Exception as price_error: return None
613
+ else: return None
614
+ except Exception as e: return None
 
 
 
 
 
615
 
616
  async def _fetch_single_timeframe_improved(self, symbol: str, timeframe: str, limit: int):
617
  """جلب بيانات إطار زمني واحد مع تحسين التعامل مع الأخطاء"""
618
  max_retries = 3
619
  retry_delay = 2
 
620
  for attempt in range(max_retries):
621
  try:
622
  ohlcv_data = self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
 
623
  if ohlcv_data and len(ohlcv_data) > 0:
624
  return ohlcv_data
625
  else:
626
  return []
 
627
  except Exception as e:
628
  if attempt < max_retries - 1:
629
  await asyncio.sleep(retry_delay * (attempt + 1))
 
633
  async def get_latest_price_async(self, symbol):
634
  """جلب السعر الحالي لعملة محددة"""
635
  try:
636
+ if not self.exchange: return None
637
+ if not symbol or '/' not in symbol: return None
 
 
 
 
638
  ticker = self.exchange.fetch_ticker(symbol)
639
+ if not ticker: return None
 
 
 
640
  current_price = ticker.get('last')
641
+ if current_price is None: return None
 
 
 
642
  return float(current_price)
643
+ except Exception as e: return None
 
 
644
 
645
  # (دوال دعم بيانات الحيتان تبقى كما هي - لا تغيير)
646
  async def get_whale_data_for_symbol(self, symbol):
 
647
  try:
648
  if self.whale_monitor:
649
  whale_data = await self.whale_monitor.get_symbol_whale_activity(symbol)
650
  return whale_data
651
+ else: return None
652
+ except Exception as e: return None
 
 
653
 
654
  async def get_whale_trading_signal(self, symbol, whale_data, market_context):
 
655
  try:
656
  if self.whale_monitor:
657
  return await self.whale_monitor.generate_whale_trading_signal(symbol, whale_data, market_context)
658
  else:
659
+ return {'action': 'HOLD', 'confidence': 0.3, 'reason': 'Whale monitor not available', 'source': 'whale_analysis'}
 
 
 
 
 
660
  except Exception as e:
661
+ return {'action': 'HOLD', 'confidence': 0.3, 'reason': f'Error: {str(e)}', 'source': 'whale_analysis'}
 
 
 
 
 
662
 
663
+ print("✅ DataManager loaded - V7.1 (1H Detector Screening)")