Riy777 commited on
Commit
a7154e1
·
1 Parent(s): 8bc0cdf

Update data_manager.py

Browse files
Files changed (1) hide show
  1. data_manager.py +50 -80
data_manager.py CHANGED
@@ -8,7 +8,6 @@ from datetime import datetime
8
  import ccxt
9
  import numpy as np
10
  import logging
11
- from typing import List, Dict, Any
12
 
13
  logging.getLogger("httpx").setLevel(logging.WARNING)
14
  logging.getLogger("httpcore").setLevel(logging.WARNING)
@@ -91,7 +90,7 @@ class DataManager:
91
  data = response.json()
92
 
93
  if 'data' not in data or not data['data']:
94
- raise ValueError("بيانات المشاعر غير متوفرة")
95
 
96
  latest_data = data['data'][0]
97
  return {
@@ -155,12 +154,16 @@ 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
@@ -176,7 +179,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'
@@ -219,33 +221,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 +257,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({
@@ -323,15 +317,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
 
@@ -365,7 +357,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,7 +369,6 @@ class DataManager:
378
 
379
  processed += len(batch)
380
 
381
- # انتظار قصير بين الدفعات
382
  if i + batch_size < len(usdt_symbols):
383
  await asyncio.sleep(1)
384
 
@@ -390,7 +380,9 @@ class DataManager:
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 +416,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 +430,15 @@ 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 +448,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 +474,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 +486,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 +500,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 +525,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 +541,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 +548,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 +571,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 +579,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 +594,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 +601,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 +612,6 @@ class DataManager:
639
  try:
640
  ohlcv_data = {}
641
 
642
- # جلب 200 شمعة لكل إطار زمني مع تحسين التعامل مع الأخطاء
643
  timeframes = [
644
  ('5m', 200),
645
  ('15m', 200),
@@ -649,40 +621,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 +674,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 +690,16 @@ 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}")
 
8
  import ccxt
9
  import numpy as np
10
  import logging
 
11
 
12
  logging.getLogger("httpx").setLevel(logging.WARNING)
13
  logging.getLogger("httpcore").setLevel(logging.WARNING)
 
90
  data = response.json()
91
 
92
  if 'data' not in data or not data['data']:
93
+ return None
94
 
95
  latest_data = data['data'][0]
96
  return {
 
154
  try:
155
  prices = {'bitcoin': None, 'ethereum': None}
156
 
157
+ btc_ticker = await asyncio.get_event_loop().run_in_executor(
158
+ None, self.exchange.fetch_ticker, 'BTC/USDT'
159
+ )
160
  btc_price = float(btc_ticker.get('last', 0)) if btc_ticker.get('last') else None
161
  if btc_price and btc_price > 0:
162
  prices['bitcoin'] = btc_price
163
 
164
+ eth_ticker = await asyncio.get_event_loop().run_in_executor(
165
+ None, self.exchange.fetch_ticker, 'ETH/USDT'
166
+ )
167
  eth_price = float(eth_ticker.get('last', 0)) if eth_ticker.get('last') else None
168
  if eth_price and eth_price > 0:
169
  prices['ethereum'] = eth_price
 
179
  await asyncio.sleep(0.5)
180
  url = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd"
181
 
 
182
  headers = {
183
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
184
  'Accept': 'application/json'
 
221
  """
222
  print("📊 الطبقة 1: جلب أفضل 200 عملة حسب حجم التداول...")
223
 
 
224
  volume_data = await self._get_volume_data_optimal()
225
 
226
  if not volume_data:
 
227
  volume_data = await self._get_volume_data_direct_api()
228
 
229
  if not volume_data:
 
230
  volume_data = await self._get_volume_data_traditional()
231
 
232
  if not volume_data:
233
  print("❌ فشل جميع محاولات جلب بيانات الأحجام")
234
  return []
235
 
 
236
  volume_data.sort(key=lambda x: x['dollar_volume'], reverse=True)
237
  top_200_by_volume = volume_data[:200]
238
 
239
  print(f"✅ تم اختيار أفضل {len(top_200_by_volume)} عملة حسب الحجم")
240
 
 
241
  final_candidates = await self._apply_advanced_indicators(top_200_by_volume)
242
 
243
  print(f"🎯 تم تحليل {len(final_candidates)} عملة للطبقة 2")
244
 
 
245
  print("🏆 أفضل 15 عملة من الطبقة 1:")
246
  for i, candidate in enumerate(final_candidates[:15]):
247
  score = candidate.get('layer1_score', 0)
 
257
  if not self.exchange:
258
  return []
259
 
260
+ tickers = await asyncio.get_event_loop().run_in_executor(
261
+ None, self.exchange.fetch_tickers
262
+ )
263
 
264
  volume_data = []
265
  processed = 0
266
 
267
  for symbol, ticker in tickers.items():
 
268
  if not symbol.endswith('/USDT'):
269
  continue
270
 
271
  if not ticker.get('active', True):
272
  continue
273
 
 
274
  current_price = ticker.get('last', 0)
275
  quote_volume = ticker.get('quoteVolume', 0)
276
 
277
  if quote_volume > 0:
278
  dollar_volume = quote_volume
279
  else:
 
280
  base_volume = ticker.get('baseVolume', 0)
281
  dollar_volume = base_volume * current_price
282
 
283
+ if dollar_volume < 50000:
 
284
  continue
285
 
286
  volume_data.append({
 
317
  for ticker in tickers:
318
  symbol = ticker['symbol']
319
 
 
320
  if not symbol.endswith('USDT'):
321
  continue
322
 
 
323
  formatted_symbol = symbol.replace('-', '/')
324
 
325
  try:
326
+ dollar_volume = float(ticker['volValue'])
327
  current_price = float(ticker['last'])
328
  price_change = float(ticker['changeRate']) * 100
329
 
 
357
  volume_data = []
358
  processed = 0
359
 
 
360
  batch_size = 50
361
  for i in range(0, len(usdt_symbols), batch_size):
362
  batch = usdt_symbols[i:i + batch_size]
 
369
 
370
  processed += len(batch)
371
 
 
372
  if i + batch_size < len(usdt_symbols):
373
  await asyncio.sleep(1)
374
 
 
380
  async def _process_single_symbol(self, symbol: str) -> Dict[str, Any]:
381
  """معالجة رمز واحد لجلب بيانات الحجم"""
382
  try:
383
+ ticker = await asyncio.get_event_loop().run_in_executor(
384
+ None, self.exchange.fetch_ticker, symbol
385
+ )
386
  if not ticker:
387
  return None
388
 
 
416
  try:
417
  symbol = symbol_data['symbol']
418
 
 
419
  detailed_data = await self._get_detailed_symbol_data(symbol)
420
  if not detailed_data:
421
  continue
422
 
 
423
  symbol_data.update(detailed_data)
424
 
 
425
  score = self._calculate_advanced_score(symbol_data)
426
  symbol_data['layer1_score'] = score
427
 
 
430
  except Exception as e:
431
  continue
432
 
 
433
  candidates.sort(key=lambda x: x.get('layer1_score', 0), reverse=True)
434
  return candidates
435
 
436
  async def _get_detailed_symbol_data(self, symbol: str) -> Dict[str, Any]:
437
  """جلب بيانات تفصيلية للرمز"""
438
  try:
439
+ ticker = await asyncio.get_event_loop().run_in_executor(
440
+ None, self.exchange.fetch_ticker, symbol
441
+ )
442
  if not ticker:
443
  return None
444
 
 
448
  open_price = ticker.get('open', 0)
449
  price_change_24h = (ticker.get('percentage', 0) or 0) * 100
450
 
 
451
  volatility = self._calculate_volatility(high_24h, low_24h, current_price)
452
  price_strength = self._calculate_price_strength(current_price, open_price, price_change_24h)
453
  momentum = self._calculate_momentum(price_change_24h)
 
474
  price_strength = symbol_data.get('price_strength', 0)
475
  momentum = symbol_data.get('momentum', 0)
476
 
 
477
  volume_score = self._calculate_volume_score(dollar_volume)
 
 
478
  momentum_score = momentum
 
 
479
  volatility_score = self._calculate_volatility_score(volatility)
 
 
480
  strength_score = price_strength
481
 
 
482
  final_score = (
483
  volume_score * 0.40 +
484
  momentum_score * 0.25 +
 
486
  strength_score * 0.15
487
  )
488
 
 
489
  reasons = []
490
  if volume_score >= 0.7:
491
  reasons.append('high_volume')
 
500
 
501
  def _calculate_volume_score(self, dollar_volume: float) -> float:
502
  """حساب درجة الحجم"""
503
+ if dollar_volume >= 10000000:
504
  return 1.0
505
+ elif dollar_volume >= 5000000:
506
  return 0.9
507
+ elif dollar_volume >= 2000000:
508
  return 0.8
509
+ elif dollar_volume >= 1000000:
510
  return 0.7
511
+ elif dollar_volume >= 500000:
512
  return 0.6
513
+ elif dollar_volume >= 250000:
514
  return 0.5
515
+ elif dollar_volume >= 100000:
516
  return 0.4
517
  else:
518
  return 0.3
 
525
 
526
  def _calculate_volatility_score(self, volatility: float) -> float:
527
  """حساب درجة التقلب"""
528
+ if 0.02 <= volatility <= 0.15:
529
  return 1.0
530
+ elif 0.01 <= volatility <= 0.20:
531
  return 0.8
532
+ elif volatility <= 0.01:
533
  return 0.4
534
+ elif volatility > 0.20:
535
  return 0.3
536
  else:
537
  return 0.5
 
541
  if open_price == 0:
542
  return 0.5
543
 
 
544
  distance_from_open = abs(current_price - open_price) / open_price
545
  change_strength = min(abs(price_change) / 50, 1.0)
546
 
 
548
 
549
  def _calculate_momentum(self, price_change: float) -> float:
550
  """حساب الزخم"""
551
+ if price_change >= 15:
552
  return 1.0
553
+ elif price_change >= 10:
554
  return 0.9
555
+ elif price_change >= 5:
556
  return 0.8
557
+ elif price_change >= 2:
558
  return 0.7
559
+ elif price_change >= 0:
560
  return 0.6
561
+ elif price_change >= -5:
562
  return 0.5
563
+ elif price_change >= -10:
564
  return 0.4
565
+ else:
566
  return 0.3
567
 
568
  async def get_ohlcv_data_for_symbols(self, symbols: List[str]) -> List[Dict[str, Any]]:
 
571
  """
572
  print(f"📊 جلب بيانات OHLCV كاملة لـ {len(symbols)} عملة بشكل متوازي...")
573
 
574
+ batch_size = 15
 
575
  batches = [symbols[i:i + batch_size] for i in range(0, len(symbols), batch_size)]
576
 
577
  all_results = []
 
579
  for batch_num, batch in enumerate(batches):
580
  print(f" 🔄 معالجة الدفعة {batch_num + 1}/{len(batches)} ({len(batch)} عملة)...")
581
 
 
582
  batch_tasks = []
583
  for symbol in batch:
584
  task = asyncio.create_task(self._fetch_complete_ohlcv_parallel(symbol))
585
  batch_tasks.append(task)
586
 
 
587
  batch_results = await asyncio.gather(*batch_tasks, return_exceptions=True)
588
 
 
589
  successful_count = 0
590
  for i, result in enumerate(batch_results):
591
  symbol = batch[i]
 
594
  elif result is not None:
595
  all_results.append(result)
596
  successful_count += 1
 
597
  timeframes_count = result.get('successful_timeframes', 0)
598
  print(f" ✅ {symbol}: {timeframes_count}/6 أطر زمنية")
599
  else:
 
601
 
602
  print(f" ✅ اكتملت الدفعة {batch_num + 1}: {successful_count}/{len(batch)} ناجحة")
603
 
 
604
  if batch_num < len(batches) - 1:
605
+ await asyncio.sleep(1)
606
 
607
  print(f"✅ تم تجميع بيانات OHLCV كاملة لـ {len(all_results)} عملة من أصل {len(symbols)}")
608
  return all_results
 
612
  try:
613
  ohlcv_data = {}
614
 
 
615
  timeframes = [
616
  ('5m', 200),
617
  ('15m', 200),
 
621
  ('1w', 200),
622
  ]
623
 
 
624
  timeframe_tasks = []
625
  for timeframe, limit in timeframes:
626
  task = asyncio.create_task(self._fetch_single_timeframe_improved(symbol, timeframe, limit))
627
  timeframe_tasks.append(task)
628
 
 
629
  timeframe_results = await asyncio.gather(*timeframe_tasks, return_exceptions=True)
630
 
 
631
  successful_timeframes = 0
632
+ min_required_timeframes = 2
633
 
 
634
  for i, (timeframe, limit) in enumerate(timeframes):
635
  result = timeframe_results[i]
636
 
637
  if isinstance(result, Exception):
638
  continue
639
 
640
+ if result and len(result) >= 10:
641
  ohlcv_data[timeframe] = result
642
  successful_timeframes += 1
643
 
 
644
  if successful_timeframes >= min_required_timeframes and ohlcv_data:
645
  try:
 
646
  current_price = await self.get_latest_price_async(symbol)
647
 
648
  result_data = {
649
  'symbol': symbol,
650
  'ohlcv': ohlcv_data,
651
+ 'raw_ohlcv': ohlcv_data,
652
  'current_price': current_price,
653
  'timestamp': datetime.now().isoformat(),
654
  'candles_count': {tf: len(data) for tf, data in ohlcv_data.items()},
 
674
 
675
  for attempt in range(max_retries):
676
  try:
677
+ ohlcv_data = await asyncio.get_event_loop().run_in_executor(
678
+ None, self.exchange.fetch_ohlcv, symbol, timeframe, limit
679
+ )
680
 
681
  if ohlcv_data and len(ohlcv_data) > 0:
682
  return ohlcv_data
 
690
  return []
691
 
692
  async def get_latest_price_async(self, symbol):
693
+ """جلب السعر الحالي لعملة محددة - الإصلاح الرئيسي هنا"""
694
  try:
695
  if not self.exchange:
696
  print(f"❌ Exchange غير مهيأ لـ {symbol}")
697
  return None
698
 
699
+ # الإصلاح: استخدام run_in_executor لتشغيل الدالة المتزامنة في thread منفصل
700
+ ticker = await asyncio.get_event_loop().run_in_executor(
701
+ None, self.exchange.fetch_ticker, symbol
702
+ )
703
 
704
  if not ticker:
705
  print(f"❌ لم يتم العثور على ticker لـ {symbol}")