Riy777 commited on
Commit
29d63db
·
1 Parent(s): 7386311

Update data_manager.py

Browse files
Files changed (1) hide show
  1. data_manager.py +58 -82
data_manager.py CHANGED
@@ -45,7 +45,9 @@ class DataManager:
45
  return
46
 
47
  print("🔄 جلب أحدث بيانات الأسواق من KuCoin...")
48
- self.exchange.load_markets()
 
 
49
  self.market_cache = self.exchange.markets
50
  self.last_market_load = datetime.now()
51
  print(f"✅ تم تحميل {len(self.market_cache)} سوق من KuCoin")
@@ -155,12 +157,15 @@ class DataManager:
155
  try:
156
  prices = {'bitcoin': None, 'ethereum': None}
157
 
158
- btc_ticker = self.exchange.fetch_ticker('BTC/USDT')
 
 
 
159
  btc_price = float(btc_ticker.get('last', 0)) if btc_ticker.get('last') else None
160
  if btc_price and btc_price > 0:
161
  prices['bitcoin'] = btc_price
162
 
163
- eth_ticker = self.exchange.fetch_ticker('ETH/USDT')
164
  eth_price = float(eth_ticker.get('last', 0)) if eth_ticker.get('last') else None
165
  if eth_price and eth_price > 0:
166
  prices['ethereum'] = eth_price
@@ -168,6 +173,7 @@ class DataManager:
168
  return prices
169
 
170
  except Exception as e:
 
171
  return {'bitcoin': None, 'ethereum': None}
172
 
173
  async def _get_prices_from_coingecko(self):
@@ -176,7 +182,6 @@ class DataManager:
176
  await asyncio.sleep(0.5)
177
  url = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd"
178
 
179
- # إضافة headers لتجنب rate limiting
180
  headers = {
181
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
182
  'Accept': 'application/json'
@@ -201,6 +206,7 @@ class DataManager:
201
  return {'bitcoin': None, 'ethereum': None}
202
 
203
  except Exception as e:
 
204
  return {'bitcoin': None, 'ethereum': None}
205
 
206
  def _get_minimal_market_context(self):
@@ -219,33 +225,27 @@ class DataManager:
219
  """
220
  print("📊 الطبقة 1: جلب أفضل 200 عملة حسب حجم التداول...")
221
 
222
- # المحاولة 1: الطريقة المثلى - استخدام fetch_tickers
223
  volume_data = await self._get_volume_data_optimal()
224
 
225
  if not volume_data:
226
- # المحاولة 2: الطريقة البديلة - استخدام API المباشر
227
  volume_data = await self._get_volume_data_direct_api()
228
 
229
  if not volume_data:
230
- # المحاولة 3: الطريقة التقليدية (الاحتياطية)
231
  volume_data = await self._get_volume_data_traditional()
232
 
233
  if not volume_data:
234
  print("❌ فشل جميع محاولات جلب بيانات الأحجام")
235
  return []
236
 
237
- # أخذ أفضل 200 عملة حسب الحجم فقط
238
  volume_data.sort(key=lambda x: x['dollar_volume'], reverse=True)
239
  top_200_by_volume = volume_data[:200]
240
 
241
  print(f"✅ تم اختيار أفضل {len(top_200_by_volume)} عملة حسب الحجم")
242
 
243
- # المرحلة 2: تطبيق المؤشرات الأخرى على الـ200 فقط
244
  final_candidates = await self._apply_advanced_indicators(top_200_by_volume)
245
 
246
  print(f"🎯 تم تحليل {len(final_candidates)} عملة للطبقة 2")
247
 
248
- # عرض أفضل 15 عملة
249
  print("🏆 أفضل 15 عملة من الطبقة 1:")
250
  for i, candidate in enumerate(final_candidates[:15]):
251
  score = candidate.get('layer1_score', 0)
@@ -261,32 +261,30 @@ class DataManager:
261
  if not self.exchange:
262
  return []
263
 
264
- tickers = self.exchange.fetch_tickers()
 
 
265
 
266
  volume_data = []
267
  processed = 0
268
 
269
  for symbol, ticker in tickers.items():
270
- # تصفية أزواج USDT النشطة فقط
271
  if not symbol.endswith('/USDT'):
272
  continue
273
 
274
  if not ticker.get('active', True):
275
  continue
276
 
277
- # استخدام quoteVolume (الحجم بالدولار) إذا متوفر
278
  current_price = ticker.get('last', 0)
279
  quote_volume = ticker.get('quoteVolume', 0)
280
 
281
  if quote_volume > 0:
282
  dollar_volume = quote_volume
283
  else:
284
- # fallback: baseVolume * السعر
285
  base_volume = ticker.get('baseVolume', 0)
286
  dollar_volume = base_volume * current_price
287
 
288
- # فلترة أولية: تجاهل العملات ذات الحجم المنخفض جداً
289
- if dollar_volume < 50000: # أقل من 50K دولار
290
  continue
291
 
292
  volume_data.append({
@@ -302,6 +300,7 @@ class DataManager:
302
  return volume_data
303
 
304
  except Exception as e:
 
305
  return []
306
 
307
  async def _get_volume_data_direct_api(self) -> List[Dict[str, Any]]:
@@ -323,15 +322,13 @@ class DataManager:
323
  for ticker in tickers:
324
  symbol = ticker['symbol']
325
 
326
- # تصفية أزواج USDT فقط
327
  if not symbol.endswith('USDT'):
328
  continue
329
 
330
- # تحويل الرمز للتنسيق القياسي (BTC-USDT → BTC/USDT)
331
  formatted_symbol = symbol.replace('-', '/')
332
 
333
  try:
334
- dollar_volume = float(ticker['volValue']) # الحجم بالدولار
335
  current_price = float(ticker['last'])
336
  price_change = float(ticker['changeRate']) * 100
337
 
@@ -349,6 +346,7 @@ class DataManager:
349
  return volume_data
350
 
351
  except Exception as e:
 
352
  return []
353
 
354
  async def _get_volume_data_traditional(self) -> List[Dict[str, Any]]:
@@ -365,7 +363,6 @@ class DataManager:
365
  volume_data = []
366
  processed = 0
367
 
368
- # معالجة دفعات لتجنب rate limits
369
  batch_size = 50
370
  for i in range(0, len(usdt_symbols), batch_size):
371
  batch = usdt_symbols[i:i + batch_size]
@@ -378,19 +375,22 @@ class DataManager:
378
 
379
  processed += len(batch)
380
 
381
- # انتظار قصير بين الدفعات
382
  if i + batch_size < len(usdt_symbols):
383
  await asyncio.sleep(1)
384
 
385
  return volume_data
386
 
387
  except Exception as e:
 
388
  return []
389
 
390
  async def _process_single_symbol(self, symbol: str) -> Dict[str, Any]:
391
  """معالجة رمز واحد لجلب بيانات الحجم"""
392
  try:
393
- ticker = self.exchange.fetch_ticker(symbol)
 
 
 
394
  if not ticker:
395
  return None
396
 
@@ -424,15 +424,12 @@ class DataManager:
424
  try:
425
  symbol = symbol_data['symbol']
426
 
427
- # جلب بيانات إضافية للرمز
428
  detailed_data = await self._get_detailed_symbol_data(symbol)
429
  if not detailed_data:
430
  continue
431
 
432
- # دمج البيانات
433
  symbol_data.update(detailed_data)
434
 
435
- # حساب الدرجة النهائية
436
  score = self._calculate_advanced_score(symbol_data)
437
  symbol_data['layer1_score'] = score
438
 
@@ -441,14 +438,16 @@ class DataManager:
441
  except Exception as e:
442
  continue
443
 
444
- # ترتيب المرشحين حسب الدرجة النهائية
445
  candidates.sort(key=lambda x: x.get('layer1_score', 0), reverse=True)
446
  return candidates
447
 
448
  async def _get_detailed_symbol_data(self, symbol: str) -> Dict[str, Any]:
449
  """جلب بيانات تفصيلية للرمز"""
450
  try:
451
- ticker = self.exchange.fetch_ticker(symbol)
 
 
 
452
  if not ticker:
453
  return None
454
 
@@ -458,7 +457,6 @@ class DataManager:
458
  open_price = ticker.get('open', 0)
459
  price_change_24h = (ticker.get('percentage', 0) or 0) * 100
460
 
461
- # حساب المؤشرات المتقدمة
462
  volatility = self._calculate_volatility(high_24h, low_24h, current_price)
463
  price_strength = self._calculate_price_strength(current_price, open_price, price_change_24h)
464
  momentum = self._calculate_momentum(price_change_24h)
@@ -485,19 +483,11 @@ class DataManager:
485
  price_strength = symbol_data.get('price_strength', 0)
486
  momentum = symbol_data.get('momentum', 0)
487
 
488
- # 1. درجة الحجم (40%) - الأهم
489
  volume_score = self._calculate_volume_score(dollar_volume)
490
-
491
- # 2. درجة الزخم (25%)
492
  momentum_score = momentum
493
-
494
- # 3. درجة التقلب (20%)
495
  volatility_score = self._calculate_volatility_score(volatility)
496
-
497
- # 4. درجة قوة السعر (15%)
498
  strength_score = price_strength
499
 
500
- # الدرجة النهائية
501
  final_score = (
502
  volume_score * 0.40 +
503
  momentum_score * 0.25 +
@@ -505,7 +495,6 @@ class DataManager:
505
  strength_score * 0.15
506
  )
507
 
508
- # تحديث أسباب الترشيح
509
  reasons = []
510
  if volume_score >= 0.7:
511
  reasons.append('high_volume')
@@ -520,19 +509,19 @@ class DataManager:
520
 
521
  def _calculate_volume_score(self, dollar_volume: float) -> float:
522
  """حساب درجة الحجم"""
523
- if dollar_volume >= 10000000: # 10M+
524
  return 1.0
525
- elif dollar_volume >= 5000000: # 5M+
526
  return 0.9
527
- elif dollar_volume >= 2000000: # 2M+
528
  return 0.8
529
- elif dollar_volume >= 1000000: # 1M+
530
  return 0.7
531
- elif dollar_volume >= 500000: # 500K+
532
  return 0.6
533
- elif dollar_volume >= 250000: # 250K+
534
  return 0.5
535
- elif dollar_volume >= 100000: # 100K+
536
  return 0.4
537
  else:
538
  return 0.3
@@ -545,13 +534,13 @@ class DataManager:
545
 
546
  def _calculate_volatility_score(self, volatility: float) -> float:
547
  """حساب درجة التقلب"""
548
- if 0.02 <= volatility <= 0.15: # تقلب مثالي 2%-15%
549
  return 1.0
550
- elif 0.01 <= volatility <= 0.20: # مقبول 1%-20%
551
  return 0.8
552
- elif volatility <= 0.01: # قليل جداً
553
  return 0.4
554
- elif volatility > 0.20: # عالي جداً
555
  return 0.3
556
  else:
557
  return 0.5
@@ -561,7 +550,6 @@ class DataManager:
561
  if open_price == 0:
562
  return 0.5
563
 
564
- # قوة السعر تعتمد على المسافة من سعر الافتتاح ونسبة التغير
565
  distance_from_open = abs(current_price - open_price) / open_price
566
  change_strength = min(abs(price_change) / 50, 1.0)
567
 
@@ -569,21 +557,21 @@ class DataManager:
569
 
570
  def _calculate_momentum(self, price_change: float) -> float:
571
  """حساب الزخم"""
572
- if price_change >= 15: # +15%+
573
  return 1.0
574
- elif price_change >= 10: # +10%+
575
  return 0.9
576
- elif price_change >= 5: # +5%+
577
  return 0.8
578
- elif price_change >= 2: # +2%+
579
  return 0.7
580
- elif price_change >= 0: # موجب
581
  return 0.6
582
- elif price_change >= -5: # حتى -5%
583
  return 0.5
584
- elif price_change >= -10: # حتى -10%
585
  return 0.4
586
- else: # أكثر من -10%
587
  return 0.3
588
 
589
  async def get_ohlcv_data_for_symbols(self, symbols: List[str]) -> List[Dict[str, Any]]:
@@ -592,8 +580,7 @@ class DataManager:
592
  """
593
  print(f"📊 جلب بيانات OHLCV كاملة لـ {len(symbols)} عملة بشكل متوازي...")
594
 
595
- # تقسيم الرموز إلى دفعات لتجنب rate limits
596
- batch_size = 15 # تقليل حجم الدفعة لتحسين الاستقرار
597
  batches = [symbols[i:i + batch_size] for i in range(0, len(symbols), batch_size)]
598
 
599
  all_results = []
@@ -601,16 +588,13 @@ class DataManager:
601
  for batch_num, batch in enumerate(batches):
602
  print(f" 🔄 معالجة الدفعة {batch_num + 1}/{len(batches)} ({len(batch)} عملة)...")
603
 
604
- # إنشاء مهام للدفعة الحالية بشكل متوازي
605
  batch_tasks = []
606
  for symbol in batch:
607
  task = asyncio.create_task(self._fetch_complete_ohlcv_parallel(symbol))
608
  batch_tasks.append(task)
609
 
610
- # انتظار انتهاء جميع مهام الدفعة الحالية
611
  batch_results = await asyncio.gather(*batch_tasks, return_exceptions=True)
612
 
613
- # معالجة نتائج الدفعة
614
  successful_count = 0
615
  for i, result in enumerate(batch_results):
616
  symbol = batch[i]
@@ -619,7 +603,6 @@ class DataManager:
619
  elif result is not None:
620
  all_results.append(result)
621
  successful_count += 1
622
- # طباعة رسالة واحدة فقط لكل عملة توضح عدد الأطر الزمنية
623
  timeframes_count = result.get('successful_timeframes', 0)
624
  print(f" ✅ {symbol}: {timeframes_count}/6 أطر زمنية")
625
  else:
@@ -627,9 +610,8 @@ class DataManager:
627
 
628
  print(f" ✅ اكتملت الدفعة {batch_num + 1}: {successful_count}/{len(batch)} ناجحة")
629
 
630
- # انتظار قصير بين الدفعات لتجنب rate limits
631
  if batch_num < len(batches) - 1:
632
- await asyncio.sleep(1) # زيادة وقت الانتظار
633
 
634
  print(f"✅ تم تجميع بيانات OHLCV كاملة لـ {len(all_results)} عملة من أصل {len(symbols)}")
635
  return all_results
@@ -639,7 +621,6 @@ class DataManager:
639
  try:
640
  ohlcv_data = {}
641
 
642
- # جلب 200 شمعة لكل إطار زمني مع تحسين التعامل مع الأخطاء
643
  timeframes = [
644
  ('5m', 200),
645
  ('15m', 200),
@@ -649,40 +630,34 @@ class DataManager:
649
  ('1w', 200),
650
  ]
651
 
652
- # إنشاء مهام لجميع الإطارات الزمنية بشكل متوازي
653
  timeframe_tasks = []
654
  for timeframe, limit in timeframes:
655
  task = asyncio.create_task(self._fetch_single_timeframe_improved(symbol, timeframe, limit))
656
  timeframe_tasks.append(task)
657
 
658
- # انتظار جميع المهام
659
  timeframe_results = await asyncio.gather(*timeframe_tasks, return_exceptions=True)
660
 
661
- # تحسين: قبول البيانات حتى لو كانت غير مكتملة
662
  successful_timeframes = 0
663
- min_required_timeframes = 2 # تخفيف الشرط من 3 إلى 2 أطر زمنية
664
 
665
- # معالجة النتائج
666
  for i, (timeframe, limit) in enumerate(timeframes):
667
  result = timeframe_results[i]
668
 
669
  if isinstance(result, Exception):
670
  continue
671
 
672
- if result and len(result) >= 10: # تخفيف الشرط من 50 إلى 10 شموع
673
  ohlcv_data[timeframe] = result
674
  successful_timeframes += 1
675
 
676
- # تحسين: قبول العملة إذا كان لديها عدد كافٍ من الأطر الزمنية
677
  if successful_timeframes >= min_required_timeframes and ohlcv_data:
678
  try:
679
- # ✅ الحصول على السعر الحالي مباشرة
680
  current_price = await self.get_latest_price_async(symbol)
681
 
682
  result_data = {
683
  'symbol': symbol,
684
  'ohlcv': ohlcv_data,
685
- 'raw_ohlcv': ohlcv_data, # ✅ إضافة البيانات الخام مباشرة
686
  'current_price': current_price,
687
  'timestamp': datetime.now().isoformat(),
688
  'candles_count': {tf: len(data) for tf, data in ohlcv_data.items()},
@@ -708,7 +683,9 @@ class DataManager:
708
 
709
  for attempt in range(max_retries):
710
  try:
711
- ohlcv_data = self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
 
 
712
 
713
  if ohlcv_data and len(ohlcv_data) > 0:
714
  return ohlcv_data
@@ -722,14 +699,15 @@ class DataManager:
722
  return []
723
 
724
  async def get_latest_price_async(self, symbol):
725
- """جلب السعر الحالي لعملة محددة"""
726
  try:
727
  if not self.exchange:
728
  print(f"❌ Exchange غير مهيأ لـ {symbol}")
729
  return None
730
 
731
- # الإصلاح الرئيسي: إزالة asyncio.create_task واستخدام fetch_ticker مباشرة
732
- ticker = self.exchange.fetch_ticker(symbol)
 
733
 
734
  if not ticker:
735
  print(f"❌ لم يتم العثور على ticker لـ {symbol}")
@@ -780,8 +758,6 @@ class DataManager:
780
  except Exception as e:
781
  return False
782
 
783
- # === الدوال الجديدة لدعم بيانات الحيتان ===
784
-
785
  async def get_whale_data_for_symbol(self, symbol):
786
  """جلب بيانات الحيتان لعملة محددة"""
787
  try:
@@ -813,4 +789,4 @@ class DataManager:
813
  'source': 'whale_analysis'
814
  }
815
 
816
- print("✅ DataManager loaded - Parallel OHLCV Fetching System with Whale Data Support & Improved Error Handling")
 
45
  return
46
 
47
  print("🔄 جلب أحدث بيانات الأسواق من KuCoin...")
48
+ # استخدام run_in_executor للدوال المتزامنة
49
+ loop = asyncio.get_event_loop()
50
+ await loop.run_in_executor(None, self.exchange.load_markets)
51
  self.market_cache = self.exchange.markets
52
  self.last_market_load = datetime.now()
53
  print(f"✅ تم تحميل {len(self.market_cache)} سوق من KuCoin")
 
157
  try:
158
  prices = {'bitcoin': None, 'ethereum': None}
159
 
160
+ # استخدام run_in_executor للدوال المتزامنة
161
+ loop = asyncio.get_event_loop()
162
+
163
+ btc_ticker = await loop.run_in_executor(None, self.exchange.fetch_ticker, 'BTC/USDT')
164
  btc_price = float(btc_ticker.get('last', 0)) if btc_ticker.get('last') else None
165
  if btc_price and btc_price > 0:
166
  prices['bitcoin'] = btc_price
167
 
168
+ eth_ticker = await loop.run_in_executor(None, self.exchange.fetch_ticker, 'ETH/USDT')
169
  eth_price = float(eth_ticker.get('last', 0)) if eth_ticker.get('last') else None
170
  if eth_price and eth_price > 0:
171
  prices['ethereum'] = eth_price
 
173
  return prices
174
 
175
  except Exception as e:
176
+ print(f"⚠️ فشل جلب الأسعار من KuCoin: {e}")
177
  return {'bitcoin': None, 'ethereum': None}
178
 
179
  async def _get_prices_from_coingecko(self):
 
182
  await asyncio.sleep(0.5)
183
  url = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd"
184
 
 
185
  headers = {
186
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
187
  'Accept': 'application/json'
 
206
  return {'bitcoin': None, 'ethereum': None}
207
 
208
  except Exception as e:
209
+ print(f"⚠️ فشل جلب الأسعار من CoinGecko: {e}")
210
  return {'bitcoin': None, 'ethereum': None}
211
 
212
  def _get_minimal_market_context(self):
 
225
  """
226
  print("📊 الطبقة 1: جلب أفضل 200 عملة حسب حجم التداول...")
227
 
 
228
  volume_data = await self._get_volume_data_optimal()
229
 
230
  if not volume_data:
 
231
  volume_data = await self._get_volume_data_direct_api()
232
 
233
  if not volume_data:
 
234
  volume_data = await self._get_volume_data_traditional()
235
 
236
  if not volume_data:
237
  print("❌ فشل جميع محاولات جلب بيانات الأحجام")
238
  return []
239
 
 
240
  volume_data.sort(key=lambda x: x['dollar_volume'], reverse=True)
241
  top_200_by_volume = volume_data[:200]
242
 
243
  print(f"✅ تم اختيار أفضل {len(top_200_by_volume)} عملة حسب الحجم")
244
 
 
245
  final_candidates = await self._apply_advanced_indicators(top_200_by_volume)
246
 
247
  print(f"🎯 تم تحليل {len(final_candidates)} عملة للطبقة 2")
248
 
 
249
  print("🏆 أفضل 15 عملة من الطبقة 1:")
250
  for i, candidate in enumerate(final_candidates[:15]):
251
  score = candidate.get('layer1_score', 0)
 
261
  if not self.exchange:
262
  return []
263
 
264
+ # استخدام run_in_executor للدوال المتزامنة
265
+ loop = asyncio.get_event_loop()
266
+ tickers = await loop.run_in_executor(None, self.exchange.fetch_tickers)
267
 
268
  volume_data = []
269
  processed = 0
270
 
271
  for symbol, ticker in tickers.items():
 
272
  if not symbol.endswith('/USDT'):
273
  continue
274
 
275
  if not ticker.get('active', True):
276
  continue
277
 
 
278
  current_price = ticker.get('last', 0)
279
  quote_volume = ticker.get('quoteVolume', 0)
280
 
281
  if quote_volume > 0:
282
  dollar_volume = quote_volume
283
  else:
 
284
  base_volume = ticker.get('baseVolume', 0)
285
  dollar_volume = base_volume * current_price
286
 
287
+ if dollar_volume < 50000:
 
288
  continue
289
 
290
  volume_data.append({
 
300
  return volume_data
301
 
302
  except Exception as e:
303
+ print(f"❌ خطأ في جلب بيانات الحجم المثلى: {e}")
304
  return []
305
 
306
  async def _get_volume_data_direct_api(self) -> List[Dict[str, Any]]:
 
322
  for ticker in tickers:
323
  symbol = ticker['symbol']
324
 
 
325
  if not symbol.endswith('USDT'):
326
  continue
327
 
 
328
  formatted_symbol = symbol.replace('-', '/')
329
 
330
  try:
331
+ dollar_volume = float(ticker['volValue'])
332
  current_price = float(ticker['last'])
333
  price_change = float(ticker['changeRate']) * 100
334
 
 
346
  return volume_data
347
 
348
  except Exception as e:
349
+ print(f"❌ خطأ في جلب بيانات الحجم المباشر: {e}")
350
  return []
351
 
352
  async def _get_volume_data_traditional(self) -> List[Dict[str, Any]]:
 
363
  volume_data = []
364
  processed = 0
365
 
 
366
  batch_size = 50
367
  for i in range(0, len(usdt_symbols), batch_size):
368
  batch = usdt_symbols[i:i + batch_size]
 
375
 
376
  processed += len(batch)
377
 
 
378
  if i + batch_size < len(usdt_symbols):
379
  await asyncio.sleep(1)
380
 
381
  return volume_data
382
 
383
  except Exception as e:
384
+ print(f"❌ خطأ في جلب بيانات الحجم التقليدية: {e}")
385
  return []
386
 
387
  async def _process_single_symbol(self, symbol: str) -> Dict[str, Any]:
388
  """معالجة رمز واحد لجلب بيانات الحجم"""
389
  try:
390
+ # استخدام run_in_executor للدوال المتزامنة
391
+ loop = asyncio.get_event_loop()
392
+ ticker = await loop.run_in_executor(None, self.exchange.fetch_ticker, symbol)
393
+
394
  if not ticker:
395
  return None
396
 
 
424
  try:
425
  symbol = symbol_data['symbol']
426
 
 
427
  detailed_data = await self._get_detailed_symbol_data(symbol)
428
  if not detailed_data:
429
  continue
430
 
 
431
  symbol_data.update(detailed_data)
432
 
 
433
  score = self._calculate_advanced_score(symbol_data)
434
  symbol_data['layer1_score'] = score
435
 
 
438
  except Exception as e:
439
  continue
440
 
 
441
  candidates.sort(key=lambda x: x.get('layer1_score', 0), reverse=True)
442
  return candidates
443
 
444
  async def _get_detailed_symbol_data(self, symbol: str) -> Dict[str, Any]:
445
  """جلب بيانات تفصيلية للرمز"""
446
  try:
447
+ # استخدام run_in_executor للدوال المتزامنة
448
+ loop = asyncio.get_event_loop()
449
+ ticker = await loop.run_in_executor(None, self.exchange.fetch_ticker, symbol)
450
+
451
  if not ticker:
452
  return None
453
 
 
457
  open_price = ticker.get('open', 0)
458
  price_change_24h = (ticker.get('percentage', 0) or 0) * 100
459
 
 
460
  volatility = self._calculate_volatility(high_24h, low_24h, current_price)
461
  price_strength = self._calculate_price_strength(current_price, open_price, price_change_24h)
462
  momentum = self._calculate_momentum(price_change_24h)
 
483
  price_strength = symbol_data.get('price_strength', 0)
484
  momentum = symbol_data.get('momentum', 0)
485
 
 
486
  volume_score = self._calculate_volume_score(dollar_volume)
 
 
487
  momentum_score = momentum
 
 
488
  volatility_score = self._calculate_volatility_score(volatility)
 
 
489
  strength_score = price_strength
490
 
 
491
  final_score = (
492
  volume_score * 0.40 +
493
  momentum_score * 0.25 +
 
495
  strength_score * 0.15
496
  )
497
 
 
498
  reasons = []
499
  if volume_score >= 0.7:
500
  reasons.append('high_volume')
 
509
 
510
  def _calculate_volume_score(self, dollar_volume: float) -> float:
511
  """حساب درجة الحجم"""
512
+ if dollar_volume >= 10000000:
513
  return 1.0
514
+ elif dollar_volume >= 5000000:
515
  return 0.9
516
+ elif dollar_volume >= 2000000:
517
  return 0.8
518
+ elif dollar_volume >= 1000000:
519
  return 0.7
520
+ elif dollar_volume >= 500000:
521
  return 0.6
522
+ elif dollar_volume >= 250000:
523
  return 0.5
524
+ elif dollar_volume >= 100000:
525
  return 0.4
526
  else:
527
  return 0.3
 
534
 
535
  def _calculate_volatility_score(self, volatility: float) -> float:
536
  """حساب درجة التقلب"""
537
+ if 0.02 <= volatility <= 0.15:
538
  return 1.0
539
+ elif 0.01 <= volatility <= 0.20:
540
  return 0.8
541
+ elif volatility <= 0.01:
542
  return 0.4
543
+ elif volatility > 0.20:
544
  return 0.3
545
  else:
546
  return 0.5
 
550
  if open_price == 0:
551
  return 0.5
552
 
 
553
  distance_from_open = abs(current_price - open_price) / open_price
554
  change_strength = min(abs(price_change) / 50, 1.0)
555
 
 
557
 
558
  def _calculate_momentum(self, price_change: float) -> float:
559
  """حساب الزخم"""
560
+ if price_change >= 15:
561
  return 1.0
562
+ elif price_change >= 10:
563
  return 0.9
564
+ elif price_change >= 5:
565
  return 0.8
566
+ elif price_change >= 2:
567
  return 0.7
568
+ elif price_change >= 0:
569
  return 0.6
570
+ elif price_change >= -5:
571
  return 0.5
572
+ elif price_change >= -10:
573
  return 0.4
574
+ else:
575
  return 0.3
576
 
577
  async def get_ohlcv_data_for_symbols(self, symbols: List[str]) -> List[Dict[str, Any]]:
 
580
  """
581
  print(f"📊 جلب بيانات OHLCV كاملة لـ {len(symbols)} عملة بشكل متوازي...")
582
 
583
+ batch_size = 15
 
584
  batches = [symbols[i:i + batch_size] for i in range(0, len(symbols), batch_size)]
585
 
586
  all_results = []
 
588
  for batch_num, batch in enumerate(batches):
589
  print(f" 🔄 معالجة الدفعة {batch_num + 1}/{len(batches)} ({len(batch)} عملة)...")
590
 
 
591
  batch_tasks = []
592
  for symbol in batch:
593
  task = asyncio.create_task(self._fetch_complete_ohlcv_parallel(symbol))
594
  batch_tasks.append(task)
595
 
 
596
  batch_results = await asyncio.gather(*batch_tasks, return_exceptions=True)
597
 
 
598
  successful_count = 0
599
  for i, result in enumerate(batch_results):
600
  symbol = batch[i]
 
603
  elif result is not None:
604
  all_results.append(result)
605
  successful_count += 1
 
606
  timeframes_count = result.get('successful_timeframes', 0)
607
  print(f" ✅ {symbol}: {timeframes_count}/6 أطر زمنية")
608
  else:
 
610
 
611
  print(f" ✅ اكتملت الدفعة {batch_num + 1}: {successful_count}/{len(batch)} ناجحة")
612
 
 
613
  if batch_num < len(batches) - 1:
614
+ await asyncio.sleep(1)
615
 
616
  print(f"✅ تم تجميع بيانات OHLCV كاملة لـ {len(all_results)} عملة من أصل {len(symbols)}")
617
  return all_results
 
621
  try:
622
  ohlcv_data = {}
623
 
 
624
  timeframes = [
625
  ('5m', 200),
626
  ('15m', 200),
 
630
  ('1w', 200),
631
  ]
632
 
 
633
  timeframe_tasks = []
634
  for timeframe, limit in timeframes:
635
  task = asyncio.create_task(self._fetch_single_timeframe_improved(symbol, timeframe, limit))
636
  timeframe_tasks.append(task)
637
 
 
638
  timeframe_results = await asyncio.gather(*timeframe_tasks, return_exceptions=True)
639
 
 
640
  successful_timeframes = 0
641
+ min_required_timeframes = 2
642
 
 
643
  for i, (timeframe, limit) in enumerate(timeframes):
644
  result = timeframe_results[i]
645
 
646
  if isinstance(result, Exception):
647
  continue
648
 
649
+ if result and len(result) >= 10:
650
  ohlcv_data[timeframe] = result
651
  successful_timeframes += 1
652
 
 
653
  if successful_timeframes >= min_required_timeframes and ohlcv_data:
654
  try:
 
655
  current_price = await self.get_latest_price_async(symbol)
656
 
657
  result_data = {
658
  'symbol': symbol,
659
  'ohlcv': ohlcv_data,
660
+ 'raw_ohlcv': ohlcv_data,
661
  'current_price': current_price,
662
  'timestamp': datetime.now().isoformat(),
663
  'candles_count': {tf: len(data) for tf, data in ohlcv_data.items()},
 
683
 
684
  for attempt in range(max_retries):
685
  try:
686
+ # استخدام run_in_executor للدوال المتزامنة
687
+ loop = asyncio.get_event_loop()
688
+ ohlcv_data = await loop.run_in_executor(None, self.exchange.fetch_ohlcv, symbol, timeframe, limit)
689
 
690
  if ohlcv_data and len(ohlcv_data) > 0:
691
  return ohlcv_data
 
699
  return []
700
 
701
  async def get_latest_price_async(self, symbol):
702
+ """جلب السعر الحالي لعملة محددة - الإصلاح الرئيسي هنا"""
703
  try:
704
  if not self.exchange:
705
  print(f"❌ Exchange غير مهيأ لـ {symbol}")
706
  return None
707
 
708
+ # استخدام run_in_executor للدوال المتزامنة بدلاً من asyncio.create_task
709
+ loop = asyncio.get_event_loop()
710
+ ticker = await loop.run_in_executor(None, self.exchange.fetch_ticker, symbol)
711
 
712
  if not ticker:
713
  print(f"❌ لم يتم العثور على ticker لـ {symbol}")
 
758
  except Exception as e:
759
  return False
760
 
 
 
761
  async def get_whale_data_for_symbol(self, symbol):
762
  """جلب بيانات الحيتان لعملة محددة"""
763
  try:
 
789
  'source': 'whale_analysis'
790
  }
791
 
792
+ print("✅ DataManager loaded - Fixed Async Wrapper for ccxt")