Update data_manager.py
Browse files- data_manager.py +98 -109
data_manager.py
CHANGED
|
@@ -280,18 +280,29 @@ class DataManager:
|
|
| 280 |
if i + self.batch_size < len(usdt_symbols):
|
| 281 |
await asyncio.sleep(0.1) # تقليل وقت الانتظار
|
| 282 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
# ترتيب المرشحين حسب قوة الأساسيات
|
| 284 |
candidates.sort(key=lambda x: x.get('layer1_score', 0), reverse=True)
|
| 285 |
|
| 286 |
-
#
|
| 287 |
-
target_count = min(max(80, len(candidates) //
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
final_candidates = candidates[:target_count]
|
| 289 |
|
| 290 |
-
print(f"✅ تم اختيار {len(final_candidates)} عملة من
|
| 291 |
|
| 292 |
-
# عرض أفضل
|
| 293 |
-
print(" 🏆 أفضل
|
| 294 |
-
for i, candidate in enumerate(final_candidates[:
|
| 295 |
score = candidate.get('layer1_score', 0)
|
| 296 |
volume = candidate.get('dollar_volume', 0)
|
| 297 |
change = candidate.get('price_change_24h', 0)
|
|
@@ -306,13 +317,7 @@ class DataManager:
|
|
| 306 |
# استخدام asyncio.gather للمعالجة المتوازية
|
| 307 |
tasks = []
|
| 308 |
for symbol in symbols_batch:
|
| 309 |
-
|
| 310 |
-
cache_key = f"ticker_{symbol}"
|
| 311 |
-
if (cache_key in self.symbol_cache and
|
| 312 |
-
time.time() - self.cache_timestamp.get(cache_key, 0) < self.cache_duration):
|
| 313 |
-
tasks.append(asyncio.create_task(self._process_cached_symbol(symbol)))
|
| 314 |
-
else:
|
| 315 |
-
tasks.append(asyncio.create_task(self._process_symbol_optimized(symbol)))
|
| 316 |
|
| 317 |
# انتظار اكتمال جميع المهام
|
| 318 |
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
@@ -324,16 +329,6 @@ class DataManager:
|
|
| 324 |
|
| 325 |
return candidates
|
| 326 |
|
| 327 |
-
async def _process_cached_symbol(self, symbol: str):
|
| 328 |
-
"""معالجة الرمز من التخزين المؤقت"""
|
| 329 |
-
cache_key = f"ticker_{symbol}"
|
| 330 |
-
cached_data = self.symbol_cache.get(cache_key)
|
| 331 |
-
if cached_data and self._validate_cached_data(cached_data):
|
| 332 |
-
return cached_data
|
| 333 |
-
else:
|
| 334 |
-
# إذا كانت البيانات المخزنة غير صالحة، معالجة جديدة
|
| 335 |
-
return await self._process_symbol_optimized(symbol)
|
| 336 |
-
|
| 337 |
async def _process_symbol_optimized(self, symbol: str):
|
| 338 |
"""معالجة رمز واحد بشكل محسن"""
|
| 339 |
try:
|
|
@@ -353,7 +348,7 @@ class DataManager:
|
|
| 353 |
high_24h = ticker.get('high', 0)
|
| 354 |
low_24h = ticker.get('low', 0)
|
| 355 |
|
| 356 |
-
# فحص سريع للمعايير الأساسية
|
| 357 |
if not self._quick_validation(current_price, dollar_volume, high_24h, low_24h):
|
| 358 |
return None
|
| 359 |
|
|
@@ -362,7 +357,8 @@ class DataManager:
|
|
| 362 |
dollar_volume, price_change_24h, current_price, high_24h, low_24h
|
| 363 |
)
|
| 364 |
|
| 365 |
-
|
|
|
|
| 366 |
candidate_data = {
|
| 367 |
'symbol': symbol,
|
| 368 |
'current_price': current_price,
|
|
@@ -375,25 +371,18 @@ class DataManager:
|
|
| 375 |
'reasons': self._generate_fast_reasons(dollar_volume, price_change_24h)
|
| 376 |
}
|
| 377 |
|
| 378 |
-
# تخزين في الذاكرة المؤقتة
|
| 379 |
-
cache_key = f"ticker_{symbol}"
|
| 380 |
-
self.symbol_cache[cache_key] = candidate_data
|
| 381 |
-
self.cache_timestamp[cache_key] = time.time()
|
| 382 |
-
|
| 383 |
return candidate_data
|
| 384 |
else:
|
| 385 |
return None
|
| 386 |
|
| 387 |
except (asyncio.TimeoutError, Exception) as e:
|
| 388 |
-
#
|
| 389 |
-
if "rate limit" not in str(e).lower():
|
| 390 |
-
return None
|
| 391 |
return None
|
| 392 |
|
| 393 |
def _quick_validation(self, current_price, dollar_volume, high_24h, low_24h):
|
| 394 |
-
"""تحقق سريع من المعايير الأساسية"""
|
| 395 |
return all([
|
| 396 |
-
dollar_volume >=
|
| 397 |
current_price > 0.00000001, # أي سعر مقبول
|
| 398 |
current_price <= 100000, # حد أقصى معقول
|
| 399 |
high_24h > 0, # بيانات سعر صالحة
|
|
@@ -404,20 +393,20 @@ class DataManager:
|
|
| 404 |
def _calculate_fast_layer1_score(self, dollar_volume, price_change, current_price, high_24h, low_24h):
|
| 405 |
"""حساب سريع لدرجة الطبقة 1"""
|
| 406 |
# حساب مبسط للحجم (بدون تعقيد)
|
| 407 |
-
volume_score = min(dollar_volume /
|
| 408 |
|
| 409 |
# حساب مبكر للزخم
|
| 410 |
-
if price_change >= 15
|
| 411 |
momentum_score = 0.9
|
| 412 |
-
elif price_change >= 8
|
| 413 |
momentum_score = 0.7
|
| 414 |
-
elif price_change >= 3
|
| 415 |
momentum_score = 0.5
|
| 416 |
-
elif price_change <= -15
|
| 417 |
momentum_score = 0.8 # فرصة شراء في الانخفاض
|
| 418 |
-
elif price_change <= -8
|
| 419 |
momentum_score = 0.6
|
| 420 |
-
elif price_change <= -3
|
| 421 |
momentum_score = 0.4
|
| 422 |
else:
|
| 423 |
momentum_score = 0.3
|
|
@@ -425,9 +414,9 @@ class DataManager:
|
|
| 425 |
# حساب موقع السعر بشكل مبسط
|
| 426 |
if high_24h != low_24h:
|
| 427 |
position = (current_price - low_24h) / (high_24h - low_24h)
|
| 428 |
-
if position < 0.3
|
| 429 |
position_score = 0.8
|
| 430 |
-
elif position > 0.7
|
| 431 |
position_score = 0.6
|
| 432 |
else:
|
| 433 |
position_score = 0.5
|
|
@@ -441,75 +430,79 @@ class DataManager:
|
|
| 441 |
"""توليد أسباب سريعة"""
|
| 442 |
reasons = []
|
| 443 |
|
| 444 |
-
if dollar_volume >=
|
| 445 |
reasons.append('very_high_volume')
|
| 446 |
-
elif dollar_volume >=
|
| 447 |
reasons.append('high_volume')
|
| 448 |
else:
|
| 449 |
reasons.append('good_liquidity')
|
| 450 |
|
| 451 |
-
if price_change >= 8
|
| 452 |
reasons.append('strong_positive_momentum')
|
| 453 |
-
elif price_change >= 3
|
| 454 |
reasons.append('positive_momentum')
|
| 455 |
-
elif price_change <= -8
|
| 456 |
reasons.append('oversold_opportunity')
|
| 457 |
-
elif price_change <= -3
|
| 458 |
reasons.append('dip_opportunity')
|
| 459 |
|
| 460 |
return reasons
|
| 461 |
|
| 462 |
-
def _validate_cached_data(self, cached_data):
|
| 463 |
-
"""التحقق من صحة البيانات المخزنة"""
|
| 464 |
-
if not cached_data:
|
| 465 |
-
return False
|
| 466 |
-
|
| 467 |
-
# التحقق من عمر البيانات
|
| 468 |
-
cache_key = f"ticker_{cached_data['symbol']}"
|
| 469 |
-
cache_time = self.cache_timestamp.get(cache_key, 0)
|
| 470 |
-
if time.time() - cache_time > self.cache_duration:
|
| 471 |
-
return False
|
| 472 |
-
|
| 473 |
-
# التحقق من صحة البيانات
|
| 474 |
-
required_fields = ['symbol', 'current_price', 'dollar_volume', 'layer1_score']
|
| 475 |
-
return all(field in cached_data for field in required_fields)
|
| 476 |
-
|
| 477 |
async def get_ohlcv_data_for_symbols(self, symbols: List[str]) -> List[Dict[str, Any]]:
|
| 478 |
"""
|
| 479 |
-
جلب بيانات OHLCV كاملة للرموز المحددة بشكل متواز
|
| 480 |
"""
|
| 481 |
results = []
|
| 482 |
|
| 483 |
print(f"📊 جلب بيانات OHLCV لـ {len(symbols)} عملة...")
|
| 484 |
|
| 485 |
-
# استخدام المهام المتوازية لجلب البيانات
|
| 486 |
tasks = []
|
| 487 |
for symbol in symbols:
|
| 488 |
-
task = asyncio.create_task(self.
|
| 489 |
tasks.append(task)
|
| 490 |
|
| 491 |
-
#
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
results.append(result)
|
| 497 |
-
except (asyncio.TimeoutError, Exception) as e:
|
| 498 |
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 499 |
|
| 500 |
-
print(f"✅ تم تجميع بيانات OHLCV لـ {len(results)} عملة")
|
| 501 |
return results
|
| 502 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 503 |
async def _fetch_ohlcv_for_symbol(self, symbol: str):
|
| 504 |
"""جلب بيانات OHLCV لرمز واحد"""
|
| 505 |
try:
|
| 506 |
ohlcv_data = {}
|
|
|
|
| 507 |
timeframes = [
|
| 508 |
-
('
|
| 509 |
-
('
|
| 510 |
-
('
|
| 511 |
-
('4h', 50),
|
| 512 |
-
('1d', 30), # تقليل للبيانات اليومية
|
| 513 |
]
|
| 514 |
|
| 515 |
has_sufficient_data = True
|
|
@@ -517,35 +510,41 @@ class DataManager:
|
|
| 517 |
try:
|
| 518 |
ohlcv = await asyncio.wait_for(
|
| 519 |
self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit),
|
| 520 |
-
timeout=
|
| 521 |
)
|
| 522 |
-
if ohlcv and len(ohlcv) >=
|
| 523 |
ohlcv_data[timeframe] = ohlcv
|
| 524 |
else:
|
| 525 |
has_sufficient_data = False
|
| 526 |
break
|
| 527 |
-
except (asyncio.TimeoutError, Exception):
|
|
|
|
| 528 |
has_sufficient_data = False
|
| 529 |
break
|
| 530 |
|
| 531 |
-
if has_sufficient_data:
|
| 532 |
# جلب السعر الحالي بشكل منفصل
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 545 |
else:
|
| 546 |
return None
|
| 547 |
|
| 548 |
except Exception as symbol_error:
|
|
|
|
| 549 |
return None
|
| 550 |
|
| 551 |
async def get_latest_price_async(self, symbol):
|
|
@@ -554,24 +553,14 @@ class DataManager:
|
|
| 554 |
if not self.exchange:
|
| 555 |
return None
|
| 556 |
|
| 557 |
-
# التحقق من التخزين المؤقت أولاً
|
| 558 |
-
cache_key = f"price_{symbol}"
|
| 559 |
-
if (cache_key in self.symbol_cache and
|
| 560 |
-
time.time() - self.cache_timestamp.get(cache_key, 0) < 10): # تخزين الأسعار لمدة 10 ثوان
|
| 561 |
-
return self.symbol_cache[cache_key].get('current_price')
|
| 562 |
-
|
| 563 |
ticker = await asyncio.wait_for(
|
| 564 |
self.exchange.fetch_ticker(symbol),
|
| 565 |
-
timeout=
|
| 566 |
)
|
| 567 |
current_price = ticker.get('last')
|
| 568 |
|
| 569 |
if current_price:
|
| 570 |
-
|
| 571 |
-
# تحديث التخزين المؤقت
|
| 572 |
-
self.symbol_cache[cache_key] = {'current_price': price}
|
| 573 |
-
self.cache_timestamp[cache_key] = time.time()
|
| 574 |
-
return price
|
| 575 |
else:
|
| 576 |
return None
|
| 577 |
|
|
|
|
| 280 |
if i + self.batch_size < len(usdt_symbols):
|
| 281 |
await asyncio.sleep(0.1) # تقليل وقت الانتظار
|
| 282 |
|
| 283 |
+
print(f"📊 تم جمع {len(candidates)} مرشح مؤهل من الطبقة 1")
|
| 284 |
+
|
| 285 |
+
if not candidates:
|
| 286 |
+
print("❌ لم يتم العثور على أي مرشح مؤهل")
|
| 287 |
+
return []
|
| 288 |
+
|
| 289 |
# ترتيب المرشحين حسب قوة الأساسيات
|
| 290 |
candidates.sort(key=lambda x: x.get('layer1_score', 0), reverse=True)
|
| 291 |
|
| 292 |
+
# إصلاح الخطأ: تحديد عدد المرشحين بشكل صحيح
|
| 293 |
+
target_count = min(max(80, len(candidates) // 2), 180, len(candidates))
|
| 294 |
+
|
| 295 |
+
# إذا كان لدينا عدد قليل من المرشحين، نأخذ نسبة أكبر
|
| 296 |
+
if len(candidates) < 100:
|
| 297 |
+
target_count = min(len(candidates), 50) # تأخذ على الأقل 50 مرشح إذا كان العدد الإجمالي قليل
|
| 298 |
+
|
| 299 |
final_candidates = candidates[:target_count]
|
| 300 |
|
| 301 |
+
print(f"✅ تم اختيار {len(final_candidates)} عملة من أصل {len(candidates)} مرشح للطبقة 2")
|
| 302 |
|
| 303 |
+
# عرض أفضل 10 مرشحين
|
| 304 |
+
print(" 🏆 أفضل 10 مرشحين من الطبقة 1:")
|
| 305 |
+
for i, candidate in enumerate(final_candidates[:10]):
|
| 306 |
score = candidate.get('layer1_score', 0)
|
| 307 |
volume = candidate.get('dollar_volume', 0)
|
| 308 |
change = candidate.get('price_change_24h', 0)
|
|
|
|
| 317 |
# استخدام asyncio.gather للمعالجة المتوازية
|
| 318 |
tasks = []
|
| 319 |
for symbol in symbols_batch:
|
| 320 |
+
tasks.append(asyncio.create_task(self._process_symbol_optimized(symbol)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 321 |
|
| 322 |
# انتظار اكتمال جميع المهام
|
| 323 |
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
|
|
| 329 |
|
| 330 |
return candidates
|
| 331 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
async def _process_symbol_optimized(self, symbol: str):
|
| 333 |
"""معالجة رمز واحد بشكل محسن"""
|
| 334 |
try:
|
|
|
|
| 348 |
high_24h = ticker.get('high', 0)
|
| 349 |
low_24h = ticker.get('low', 0)
|
| 350 |
|
| 351 |
+
# فحص سريع للمعايير الأساسية - تخفيف الشروط
|
| 352 |
if not self._quick_validation(current_price, dollar_volume, high_24h, low_24h):
|
| 353 |
return None
|
| 354 |
|
|
|
|
| 357 |
dollar_volume, price_change_24h, current_price, high_24h, low_24h
|
| 358 |
)
|
| 359 |
|
| 360 |
+
# تخفيف عتبة القبول - تقليل من 0.3 إلى 0.1
|
| 361 |
+
if layer1_score >= 0.1: # كان 0.3
|
| 362 |
candidate_data = {
|
| 363 |
'symbol': symbol,
|
| 364 |
'current_price': current_price,
|
|
|
|
| 371 |
'reasons': self._generate_fast_reasons(dollar_volume, price_change_24h)
|
| 372 |
}
|
| 373 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
return candidate_data
|
| 375 |
else:
|
| 376 |
return None
|
| 377 |
|
| 378 |
except (asyncio.TimeoutError, Exception) as e:
|
| 379 |
+
# تجاهل الأخطاء والمتابعة
|
|
|
|
|
|
|
| 380 |
return None
|
| 381 |
|
| 382 |
def _quick_validation(self, current_price, dollar_volume, high_24h, low_24h):
|
| 383 |
+
"""تحقق سريع من المعايير الأساسية - تخفيف الشروط"""
|
| 384 |
return all([
|
| 385 |
+
dollar_volume >= 500000, # تخفيف من 1M إلى 500K دولار
|
| 386 |
current_price > 0.00000001, # أي سعر مقبول
|
| 387 |
current_price <= 100000, # حد أقصى معقول
|
| 388 |
high_24h > 0, # بيانات سعر صالحة
|
|
|
|
| 393 |
def _calculate_fast_layer1_score(self, dollar_volume, price_change, current_price, high_24h, low_24h):
|
| 394 |
"""حساب سريع لدرجة الطبقة 1"""
|
| 395 |
# حساب مبسط للحجم (بدون تعقيد)
|
| 396 |
+
volume_score = min(dollar_volume / 2000000, 1.0) # تخفيف من 5M إلى 2M
|
| 397 |
|
| 398 |
# حساب مبكر للزخم
|
| 399 |
+
if price_change >= 10: # تخفيف من 15 إلى 10
|
| 400 |
momentum_score = 0.9
|
| 401 |
+
elif price_change >= 5: # تخفيف من 8 إلى 5
|
| 402 |
momentum_score = 0.7
|
| 403 |
+
elif price_change >= 2: # تخفيف من 3 إلى 2
|
| 404 |
momentum_score = 0.5
|
| 405 |
+
elif price_change <= -10: # تخفيف من -15 إلى -10
|
| 406 |
momentum_score = 0.8 # فرصة شراء في الانخفاض
|
| 407 |
+
elif price_change <= -5: # تخفيف من -8 إلى -5
|
| 408 |
momentum_score = 0.6
|
| 409 |
+
elif price_change <= -2: # تخفيف من -3 إلى -2
|
| 410 |
momentum_score = 0.4
|
| 411 |
else:
|
| 412 |
momentum_score = 0.3
|
|
|
|
| 414 |
# حساب موقع السعر بشكل مبسط
|
| 415 |
if high_24h != low_24h:
|
| 416 |
position = (current_price - low_24h) / (high_24h - low_24h)
|
| 417 |
+
if position < 0.2: # كان 0.3
|
| 418 |
position_score = 0.8
|
| 419 |
+
elif position > 0.8: # كان 0.7
|
| 420 |
position_score = 0.6
|
| 421 |
else:
|
| 422 |
position_score = 0.5
|
|
|
|
| 430 |
"""توليد أسباب سريعة"""
|
| 431 |
reasons = []
|
| 432 |
|
| 433 |
+
if dollar_volume >= 2000000: # تخفيف من 5M إلى 2M
|
| 434 |
reasons.append('very_high_volume')
|
| 435 |
+
elif dollar_volume >= 1000000: # تخفيف من 2M إلى 1M
|
| 436 |
reasons.append('high_volume')
|
| 437 |
else:
|
| 438 |
reasons.append('good_liquidity')
|
| 439 |
|
| 440 |
+
if price_change >= 5: # تخفيف من 8 إلى 5
|
| 441 |
reasons.append('strong_positive_momentum')
|
| 442 |
+
elif price_change >= 2: # تخفيف من 3 إلى 2
|
| 443 |
reasons.append('positive_momentum')
|
| 444 |
+
elif price_change <= -5: # تخفيف من -8 إلى -5
|
| 445 |
reasons.append('oversold_opportunity')
|
| 446 |
+
elif price_change <= -2: # تخفيف من -3 إلى -2
|
| 447 |
reasons.append('dip_opportunity')
|
| 448 |
|
| 449 |
return reasons
|
| 450 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 451 |
async def get_ohlcv_data_for_symbols(self, symbols: List[str]) -> List[Dict[str, Any]]:
|
| 452 |
"""
|
| 453 |
+
جلب بيانات OHLCV كاملة للرموز المحددة بشكل متواز ومحسن
|
| 454 |
"""
|
| 455 |
results = []
|
| 456 |
|
| 457 |
print(f"📊 جلب بيانات OHLCV لـ {len(symbols)} عملة...")
|
| 458 |
|
| 459 |
+
# استخدام المهام المتوازية لجلب البيانات مع إعادة المحاولة
|
| 460 |
tasks = []
|
| 461 |
for symbol in symbols:
|
| 462 |
+
task = asyncio.create_task(self._fetch_ohlcv_with_retry(symbol))
|
| 463 |
tasks.append(task)
|
| 464 |
|
| 465 |
+
# جمع النتائج مع معالجة الأخطاء
|
| 466 |
+
completed_tasks = await asyncio.gather(*tasks, return_exceptions=True)
|
| 467 |
+
|
| 468 |
+
for result in completed_tasks:
|
| 469 |
+
if isinstance(result, Exception) or result is None:
|
|
|
|
|
|
|
| 470 |
continue
|
| 471 |
+
results.append(result)
|
| 472 |
+
|
| 473 |
+
print(f"✅ تم تجميع بيانات OHLCV لـ {len(results)} عملة من أصل {len(symbols)}")
|
| 474 |
+
|
| 475 |
+
if len(results) < len(symbols):
|
| 476 |
+
print(f"⚠️ فشل جلب بيانات {len(symbols) - len(results)} عملة")
|
| 477 |
|
|
|
|
| 478 |
return results
|
| 479 |
|
| 480 |
+
async def _fetch_ohlcv_with_retry(self, symbol: str, max_retries: int = 3):
|
| 481 |
+
"""جلب بيانات OHLCV مع إعادة المحاولة"""
|
| 482 |
+
for attempt in range(max_retries):
|
| 483 |
+
try:
|
| 484 |
+
result = await self._fetch_ohlcv_for_symbol(symbol)
|
| 485 |
+
if result:
|
| 486 |
+
return result
|
| 487 |
+
else:
|
| 488 |
+
await asyncio.sleep(1) # انتظار قبل إعادة المحاولة
|
| 489 |
+
except Exception as e:
|
| 490 |
+
if attempt < max_retries - 1:
|
| 491 |
+
await asyncio.sleep(1)
|
| 492 |
+
continue
|
| 493 |
+
else:
|
| 494 |
+
print(f"❌ فشل جلب بيانات {symbol} بعد {max_retries} محاولات: {e}")
|
| 495 |
+
return None
|
| 496 |
+
|
| 497 |
async def _fetch_ohlcv_for_symbol(self, symbol: str):
|
| 498 |
"""جلب بيانات OHLCV لرمز واحد"""
|
| 499 |
try:
|
| 500 |
ohlcv_data = {}
|
| 501 |
+
# تقليل عدد الإطارات الزمنية لتسريع العملية
|
| 502 |
timeframes = [
|
| 503 |
+
('1h', 50), # إطار ساعة واحد فقط للبيانات الأساسية
|
| 504 |
+
('4h', 30), # إطار 4 ساعات
|
| 505 |
+
('1d', 20), # إطار يومي
|
|
|
|
|
|
|
| 506 |
]
|
| 507 |
|
| 508 |
has_sufficient_data = True
|
|
|
|
| 510 |
try:
|
| 511 |
ohlcv = await asyncio.wait_for(
|
| 512 |
self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit),
|
| 513 |
+
timeout=10 # زيادة المهلة
|
| 514 |
)
|
| 515 |
+
if ohlcv and len(ohlcv) >= 10: # تقليل الحد الأدنى
|
| 516 |
ohlcv_data[timeframe] = ohlcv
|
| 517 |
else:
|
| 518 |
has_sufficient_data = False
|
| 519 |
break
|
| 520 |
+
except (asyncio.TimeoutError, Exception) as e:
|
| 521 |
+
print(f"⚠️ خطأ في جلب بيانات {timeframe} لـ {symbol}: {e}")
|
| 522 |
has_sufficient_data = False
|
| 523 |
break
|
| 524 |
|
| 525 |
+
if has_sufficient_data and ohlcv_data: # التأكد من وجود بيانات
|
| 526 |
# جلب السعر الحالي بشكل منفصل
|
| 527 |
+
try:
|
| 528 |
+
ticker = await asyncio.wait_for(
|
| 529 |
+
self.exchange.fetch_ticker(symbol),
|
| 530 |
+
timeout=5
|
| 531 |
+
)
|
| 532 |
+
current_price = ticker.get('last', 0) if ticker else 0
|
| 533 |
+
|
| 534 |
+
return {
|
| 535 |
+
'symbol': symbol,
|
| 536 |
+
'ohlcv': ohlcv_data,
|
| 537 |
+
'current_price': current_price,
|
| 538 |
+
'timestamp': datetime.now().isoformat()
|
| 539 |
+
}
|
| 540 |
+
except Exception as price_error:
|
| 541 |
+
print(f"⚠️ خطأ في جلب السعر لـ {symbol}: {price_error}")
|
| 542 |
+
return None
|
| 543 |
else:
|
| 544 |
return None
|
| 545 |
|
| 546 |
except Exception as symbol_error:
|
| 547 |
+
print(f"⚠️ خطأ عام في جلب بيانات {symbol}: {symbol_error}")
|
| 548 |
return None
|
| 549 |
|
| 550 |
async def get_latest_price_async(self, symbol):
|
|
|
|
| 553 |
if not self.exchange:
|
| 554 |
return None
|
| 555 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 556 |
ticker = await asyncio.wait_for(
|
| 557 |
self.exchange.fetch_ticker(symbol),
|
| 558 |
+
timeout=5
|
| 559 |
)
|
| 560 |
current_price = ticker.get('last')
|
| 561 |
|
| 562 |
if current_price:
|
| 563 |
+
return float(current_price)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 564 |
else:
|
| 565 |
return None
|
| 566 |
|