Riy777 commited on
Commit
396f10a
·
1 Parent(s): 394e2c7

Update data_manager.py

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