Spaces:
Building
Building
Update data_manager.py
Browse files- data_manager.py +327 -48
data_manager.py
CHANGED
|
@@ -199,7 +199,6 @@ class DataManager:
|
|
| 199 |
sentiment_task = asyncio.wait_for(self.get_sentiment_safe_async(), timeout=10)
|
| 200 |
price_task = asyncio.wait_for(self._get_prices_with_fallback(), timeout=15)
|
| 201 |
|
| 202 |
-
# ❌ إزالة مهمة الحيتان العامة
|
| 203 |
results = await asyncio.gather(sentiment_task, price_task, return_exceptions=True)
|
| 204 |
|
| 205 |
sentiment_data = results[0] if not isinstance(results[0], Exception) else None
|
|
@@ -217,7 +216,6 @@ class DataManager:
|
|
| 217 |
|
| 218 |
market_trend = self._determine_market_trend(bitcoin_price, sentiment_data)
|
| 219 |
|
| 220 |
-
# ❌ إزالة تحليل إشارات التداول المعتمدة على الحيتان العامة
|
| 221 |
trading_decision = self._analyze_market_trading_signals(sentiment_data)
|
| 222 |
|
| 223 |
market_context = {
|
|
@@ -226,7 +224,6 @@ class DataManager:
|
|
| 226 |
'ethereum_price_usd': ethereum_price,
|
| 227 |
'fear_and_greed_index': sentiment_data.get('feargreed_value') if sentiment_data else None,
|
| 228 |
'sentiment_class': sentiment_data.get('feargreed_class') if sentiment_data else 'UNKNOWN',
|
| 229 |
-
# ❌ إزالة بيانات الحيتان العامة
|
| 230 |
'general_whale_activity': {
|
| 231 |
'data_available': False,
|
| 232 |
'description': 'نظام الحيتان العام معطل - يركز النظام على تحليل الحيتان للعملات المرشحة فقط',
|
|
@@ -240,11 +237,11 @@ class DataManager:
|
|
| 240 |
'data_sources': {
|
| 241 |
'prices': bitcoin_price is not None and ethereum_price is not None,
|
| 242 |
'sentiment': sentiment_data is not None,
|
| 243 |
-
'general_whale_data': False,
|
| 244 |
-
'netflow_analysis': 'DISABLED'
|
| 245 |
},
|
| 246 |
'data_quality': 'HIGH',
|
| 247 |
-
'risk_assessment': self._assess_market_risk(sentiment_data)
|
| 248 |
}
|
| 249 |
|
| 250 |
return market_context
|
|
@@ -257,7 +254,7 @@ class DataManager:
|
|
| 257 |
return self._get_minimal_market_context()
|
| 258 |
|
| 259 |
def _analyze_market_trading_signals(self, sentiment_data):
|
| 260 |
-
"""تحليل إشارات التداول بناءً على مشاعر السوق فقط
|
| 261 |
if not sentiment_data:
|
| 262 |
return {
|
| 263 |
'action': 'HOLD',
|
|
@@ -292,7 +289,7 @@ class DataManager:
|
|
| 292 |
}
|
| 293 |
|
| 294 |
def _assess_market_risk(self, sentiment_data):
|
| 295 |
-
"""تقييم مخاطر السوق بناءً على المشاعر فقط
|
| 296 |
risk_factors = []
|
| 297 |
risk_score = 0
|
| 298 |
|
|
@@ -472,7 +469,6 @@ class DataManager:
|
|
| 472 |
'last_market_load': self.last_market_load.isoformat() if self.last_market_load else None
|
| 473 |
}
|
| 474 |
|
| 475 |
-
# ❌ إزالة إحصائيات API للحيتان العامة
|
| 476 |
stats['api_usage'] = {
|
| 477 |
'note': 'نظام الحيتان العام معطل - يتم تحليل الحيتان للعملات المرشحة فقط'
|
| 478 |
}
|
|
@@ -480,7 +476,7 @@ class DataManager:
|
|
| 480 |
return stats
|
| 481 |
|
| 482 |
async def get_symbol_specific_whale_data(self, symbol, contract_address=None):
|
| 483 |
-
"""جلب بيانات الحيتان الخاصة برمز معين
|
| 484 |
if hasattr(self.whale_monitor, 'get_symbol_whale_activity'):
|
| 485 |
return await self.whale_monitor.get_symbol_whale_activity(symbol, contract_address)
|
| 486 |
else:
|
|
@@ -503,6 +499,14 @@ class DataManager:
|
|
| 503 |
}
|
| 504 |
|
| 505 |
async def _calculate_technical_score(self, symbol, ohlcv_data):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 506 |
try:
|
| 507 |
if not ohlcv_data or '1h' not in ohlcv_data:
|
| 508 |
return 0.0
|
|
@@ -515,14 +519,78 @@ class DataManager:
|
|
| 515 |
volumes = np.array([candle[5] for candle in hourly_data])
|
| 516 |
highs = np.array([candle[2] for candle in hourly_data])
|
| 517 |
lows = np.array([candle[3] for candle in hourly_data])
|
|
|
|
| 518 |
|
| 519 |
-
|
| 520 |
-
|
|
|
|
|
|
|
| 521 |
|
| 522 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 523 |
avg_volume_50 = np.mean(volumes[-50:])
|
| 524 |
volume_ratio = recent_volume / avg_volume_50 if avg_volume_50 > 0 else 1.0
|
| 525 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 526 |
|
| 527 |
true_ranges = []
|
| 528 |
for i in range(1, len(hourly_data)):
|
|
@@ -532,33 +600,91 @@ class DataManager:
|
|
| 532 |
tr3 = abs(low - prev_close)
|
| 533 |
true_ranges.append(max(tr1, tr2, tr3))
|
| 534 |
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 538 |
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 542 |
|
| 543 |
-
|
| 544 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 545 |
|
| 546 |
final_score = (
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
|
|
|
|
|
|
|
| 551 |
)
|
| 552 |
|
| 553 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 554 |
|
| 555 |
except Exception as e:
|
| 556 |
print(f"❌ خطأ في حساب الدرجة التقنية لـ {symbol}: {e}")
|
| 557 |
return 0.0
|
| 558 |
|
| 559 |
async def find_high_potential_candidates(self, count=20):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 560 |
try:
|
| 561 |
-
print(f"🔍 البحث عن {count}
|
| 562 |
|
| 563 |
if not self.exchange:
|
| 564 |
print("❌ لا يوجد اتصال بـ KuCoin")
|
|
@@ -567,6 +693,7 @@ class DataManager:
|
|
| 567 |
if not self.market_cache or not self.last_market_load or (datetime.now() - self.last_market_load).total_seconds() > 3600:
|
| 568 |
await self._load_markets()
|
| 569 |
|
|
|
|
| 570 |
usdt_symbols = [
|
| 571 |
symbol for symbol in self.market_cache.keys()
|
| 572 |
if symbol.endswith('/USDT') and self.market_cache[symbol].get('active', False)
|
|
@@ -574,63 +701,215 @@ class DataManager:
|
|
| 574 |
|
| 575 |
print(f"✅ تم العثور على {len(usdt_symbols)} رمز USDT نشط في KuCoin")
|
| 576 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 577 |
candidates_with_scores = []
|
| 578 |
analyzed_count = 0
|
| 579 |
|
| 580 |
-
|
|
|
|
|
|
|
|
|
|
| 581 |
try:
|
|
|
|
| 582 |
analyzed_count += 1
|
|
|
|
| 583 |
if analyzed_count % 10 == 0:
|
| 584 |
-
print(f"
|
| 585 |
|
| 586 |
-
|
|
|
|
| 587 |
|
| 588 |
-
if not ohlcv_1h or len(ohlcv_1h) <
|
| 589 |
continue
|
| 590 |
|
| 591 |
ohlcv_data = {'1h': ohlcv_1h}
|
| 592 |
|
|
|
|
| 593 |
technical_score = await self._calculate_technical_score(symbol, ohlcv_data)
|
| 594 |
|
| 595 |
-
if technical_score > 0.3:
|
| 596 |
-
|
|
|
|
| 597 |
|
| 598 |
-
reasons = []
|
| 599 |
if technical_score > 0.7:
|
| 600 |
-
reasons.append('
|
| 601 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 602 |
reasons.append('high_liquidity')
|
| 603 |
-
if abs(ticker.get('percentage', 0)) > 5:
|
| 604 |
-
reasons.append('significant_momentum')
|
| 605 |
|
| 606 |
candidates_with_scores.append({
|
| 607 |
'symbol': symbol,
|
| 608 |
'technical_score': technical_score,
|
| 609 |
'reasons': reasons,
|
| 610 |
-
'volume':
|
| 611 |
-
'
|
| 612 |
-
'
|
|
|
|
|
|
|
| 613 |
})
|
| 614 |
|
| 615 |
except Exception as e:
|
| 616 |
if "rate limit" not in str(e).lower():
|
| 617 |
-
print(f"⚠️ خطأ في
|
| 618 |
continue
|
| 619 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 620 |
candidates_with_scores.sort(key=lambda x: x['technical_score'], reverse=True)
|
| 621 |
-
top_candidates = candidates_with_scores[:count]
|
| 622 |
|
| 623 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 624 |
|
| 625 |
-
|
| 626 |
-
|
| 627 |
|
| 628 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 629 |
|
| 630 |
except Exception as e:
|
| 631 |
print(f"❌ خطأ في find_high_potential_candidates: {e}")
|
|
|
|
|
|
|
| 632 |
return []
|
| 633 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 634 |
async def get_fast_pass_data_async(self, candidates):
|
| 635 |
try:
|
| 636 |
print(f"📊 جلب بيانات OHLCV لـ {len(candidates)} مرشح من KuCoin...")
|
|
|
|
| 199 |
sentiment_task = asyncio.wait_for(self.get_sentiment_safe_async(), timeout=10)
|
| 200 |
price_task = asyncio.wait_for(self._get_prices_with_fallback(), timeout=15)
|
| 201 |
|
|
|
|
| 202 |
results = await asyncio.gather(sentiment_task, price_task, return_exceptions=True)
|
| 203 |
|
| 204 |
sentiment_data = results[0] if not isinstance(results[0], Exception) else None
|
|
|
|
| 216 |
|
| 217 |
market_trend = self._determine_market_trend(bitcoin_price, sentiment_data)
|
| 218 |
|
|
|
|
| 219 |
trading_decision = self._analyze_market_trading_signals(sentiment_data)
|
| 220 |
|
| 221 |
market_context = {
|
|
|
|
| 224 |
'ethereum_price_usd': ethereum_price,
|
| 225 |
'fear_and_greed_index': sentiment_data.get('feargreed_value') if sentiment_data else None,
|
| 226 |
'sentiment_class': sentiment_data.get('feargreed_class') if sentiment_data else 'UNKNOWN',
|
|
|
|
| 227 |
'general_whale_activity': {
|
| 228 |
'data_available': False,
|
| 229 |
'description': 'نظام الحيتان العام معطل - يركز النظام على تحليل الحيتان للعملات المرشحة فقط',
|
|
|
|
| 237 |
'data_sources': {
|
| 238 |
'prices': bitcoin_price is not None and ethereum_price is not None,
|
| 239 |
'sentiment': sentiment_data is not None,
|
| 240 |
+
'general_whale_data': False,
|
| 241 |
+
'netflow_analysis': 'DISABLED'
|
| 242 |
},
|
| 243 |
'data_quality': 'HIGH',
|
| 244 |
+
'risk_assessment': self._assess_market_risk(sentiment_data)
|
| 245 |
}
|
| 246 |
|
| 247 |
return market_context
|
|
|
|
| 254 |
return self._get_minimal_market_context()
|
| 255 |
|
| 256 |
def _analyze_market_trading_signals(self, sentiment_data):
|
| 257 |
+
"""تحليل إشارات التداول بناءً على مشاعر السوق فقط"""
|
| 258 |
if not sentiment_data:
|
| 259 |
return {
|
| 260 |
'action': 'HOLD',
|
|
|
|
| 289 |
}
|
| 290 |
|
| 291 |
def _assess_market_risk(self, sentiment_data):
|
| 292 |
+
"""تقييم مخاطر السوق بناءً على المشاعر فقط"""
|
| 293 |
risk_factors = []
|
| 294 |
risk_score = 0
|
| 295 |
|
|
|
|
| 469 |
'last_market_load': self.last_market_load.isoformat() if self.last_market_load else None
|
| 470 |
}
|
| 471 |
|
|
|
|
| 472 |
stats['api_usage'] = {
|
| 473 |
'note': 'نظام الحيتان العام معطل - يتم تحليل الحيتان للعملات المرشحة فقط'
|
| 474 |
}
|
|
|
|
| 476 |
return stats
|
| 477 |
|
| 478 |
async def get_symbol_specific_whale_data(self, symbol, contract_address=None):
|
| 479 |
+
"""جلب بيانات الحيتان الخاصة برمز معين"""
|
| 480 |
if hasattr(self.whale_monitor, 'get_symbol_whale_activity'):
|
| 481 |
return await self.whale_monitor.get_symbol_whale_activity(symbol, contract_address)
|
| 482 |
else:
|
|
|
|
| 499 |
}
|
| 500 |
|
| 501 |
async def _calculate_technical_score(self, symbol, ohlcv_data):
|
| 502 |
+
"""
|
| 503 |
+
حساب درجة تقنية شاملة تعتمد على:
|
| 504 |
+
- الزخم السعري (قصير ومتوسط المدى)
|
| 505 |
+
- السيولة والحجم
|
| 506 |
+
- قوة الاتجاه
|
| 507 |
+
- التقلب
|
| 508 |
+
- الزخم الحجمي
|
| 509 |
+
"""
|
| 510 |
try:
|
| 511 |
if not ohlcv_data or '1h' not in ohlcv_data:
|
| 512 |
return 0.0
|
|
|
|
| 519 |
volumes = np.array([candle[5] for candle in hourly_data])
|
| 520 |
highs = np.array([candle[2] for candle in hourly_data])
|
| 521 |
lows = np.array([candle[3] for candle in hourly_data])
|
| 522 |
+
opens = np.array([candle[1] for candle in hourly_data])
|
| 523 |
|
| 524 |
+
current_price = closes[-1]
|
| 525 |
+
|
| 526 |
+
# 1. تقييم الزخم السعري (Momentum) - الوزن الأكبر
|
| 527 |
+
momentum_score = 0.0
|
| 528 |
|
| 529 |
+
# زخم قصير المدى (1-4 ساعات)
|
| 530 |
+
if len(closes) >= 5:
|
| 531 |
+
price_change_1h = ((closes[-1] - closes[-2]) / closes[-2]) * 100
|
| 532 |
+
price_change_4h = ((closes[-1] - closes[-5]) / closes[-5]) * 100
|
| 533 |
+
|
| 534 |
+
# نفضل الزخم الإيجابي المعتدل (2%-10%)
|
| 535 |
+
if 2 <= price_change_1h <= 10:
|
| 536 |
+
momentum_score += 0.3
|
| 537 |
+
elif 2 <= price_change_4h <= 15:
|
| 538 |
+
momentum_score += 0.2
|
| 539 |
+
|
| 540 |
+
# زخم متوسط المدى (12-24 ساعة)
|
| 541 |
+
if len(closes) >= 25:
|
| 542 |
+
price_change_12h = ((closes[-1] - closes[-13]) / closes[-13]) * 100
|
| 543 |
+
price_change_24h = ((closes[-1] - closes[-25]) / closes[-25]) * 100
|
| 544 |
+
|
| 545 |
+
if 5 <= price_change_12h <= 20:
|
| 546 |
+
momentum_score += 0.2
|
| 547 |
+
if 8 <= price_change_24h <= 25:
|
| 548 |
+
momentum_score += 0.2
|
| 549 |
+
|
| 550 |
+
# 2. تقييم السيولة والحجم (Liquidity & Volume)
|
| 551 |
+
liquidity_score = 0.0
|
| 552 |
+
|
| 553 |
+
# متوسط الحجم بالدولار
|
| 554 |
+
avg_dollar_volume = np.mean(volumes[-50:]) * np.mean(closes[-50:])
|
| 555 |
+
if avg_dollar_volume > 1000000: # أكثر من 1 مليون دولار
|
| 556 |
+
liquidity_score += 0.4
|
| 557 |
+
elif avg_dollar_volume > 500000: # أكثر من 500 ألف دولار
|
| 558 |
+
liquidity_score += 0.3
|
| 559 |
+
elif avg_dollar_volume > 100000: # أكثر من 100 ألف دولار
|
| 560 |
+
liquidity_score += 0.2
|
| 561 |
+
|
| 562 |
+
# نسبة الحجم الحديث إلى المتوسط
|
| 563 |
+
recent_volume = np.mean(volumes[-6:]) # آخر 6 ساعات
|
| 564 |
avg_volume_50 = np.mean(volumes[-50:])
|
| 565 |
volume_ratio = recent_volume / avg_volume_50 if avg_volume_50 > 0 else 1.0
|
| 566 |
+
|
| 567 |
+
if volume_ratio > 1.5: # حجم مرتفع حديثاً
|
| 568 |
+
liquidity_score += 0.3
|
| 569 |
+
elif volume_ratio > 1.2:
|
| 570 |
+
liquidity_score += 0.2
|
| 571 |
+
|
| 572 |
+
# 3. تقييم قوة الاتجاه (Trend Strength)
|
| 573 |
+
trend_score = 0.0
|
| 574 |
+
|
| 575 |
+
if len(closes) >= 20:
|
| 576 |
+
# المتوسطات المتحركة
|
| 577 |
+
ma_short = np.mean(closes[-5:]) # 5 ساعات
|
| 578 |
+
ma_medium = np.mean(closes[-13:]) # 13 ساعة
|
| 579 |
+
ma_long = np.mean(closes[-21:]) # 21 ساعة
|
| 580 |
+
|
| 581 |
+
# تأكيد الاتجاه (جميع المتوسطات في نفس الاتجاه)
|
| 582 |
+
if ma_short > ma_medium > ma_long:
|
| 583 |
+
trend_score += 0.4 # اتجاه صاعد قوي
|
| 584 |
+
elif ma_short < ma_medium < ma_long:
|
| 585 |
+
trend_score += 0.2 # اتجاه هابط
|
| 586 |
+
|
| 587 |
+
# قوة الاتجاه (المسافة بين المتوسطات)
|
| 588 |
+
trend_strength = (ma_short - ma_long) / ma_long * 100
|
| 589 |
+
if abs(trend_strength) > 3: # اتجاه قوي (>3%)
|
| 590 |
+
trend_score += 0.2
|
| 591 |
+
|
| 592 |
+
# 4. تقييم التقلب (Volatility) - نفضل التقلب المعتدل
|
| 593 |
+
volatility_score = 0.0
|
| 594 |
|
| 595 |
true_ranges = []
|
| 596 |
for i in range(1, len(hourly_data)):
|
|
|
|
| 600 |
tr3 = abs(low - prev_close)
|
| 601 |
true_ranges.append(max(tr1, tr2, tr3))
|
| 602 |
|
| 603 |
+
if true_ranges:
|
| 604 |
+
atr = np.mean(true_ranges[-14:])
|
| 605 |
+
atr_percent = (atr / current_price) * 100
|
| 606 |
+
|
| 607 |
+
# نفضل التقلب بين 2% و 8% (فرص تداول جيدة)
|
| 608 |
+
if 2 <= atr_percent <= 8:
|
| 609 |
+
volatility_score = 0.3
|
| 610 |
+
elif atr_percent > 8:
|
| 611 |
+
volatility_score = 0.1 # تقلب عالي خطير
|
| 612 |
+
else:
|
| 613 |
+
volatility_score = 0.1 # تقلب منخفض قليل الفرص
|
| 614 |
+
|
| 615 |
+
# 5. تقييم الزخم الحجمي (Volume Momentum)
|
| 616 |
+
volume_momentum_score = 0.0
|
| 617 |
|
| 618 |
+
if len(volumes) >= 10:
|
| 619 |
+
# تسارع الحجم (الحجم الأخير vs المتوسط)
|
| 620 |
+
volume_acceleration = (volumes[-1] - np.mean(volumes[-10:])) / np.mean(volumes[-10:])
|
| 621 |
+
if volume_acceleration > 0.5: # زيادة حجم بنسبة 50%
|
| 622 |
+
volume_momentum_score = 0.2
|
| 623 |
+
|
| 624 |
+
# استمرارية الحجم المرتفع
|
| 625 |
+
above_avg_volume_count = sum(1 for vol in volumes[-5:] if vol > np.mean(volumes[-20:]))
|
| 626 |
+
if above_avg_volume_count >= 3: # 3 من آخر 5 ساعات فوق المتوسط
|
| 627 |
+
volume_momentum_score += 0.2
|
| 628 |
+
|
| 629 |
+
# 6. تقييم كفاءة الحركة (Price Efficiency)
|
| 630 |
+
efficiency_score = 0.0
|
| 631 |
|
| 632 |
+
if len(closes) >= 20:
|
| 633 |
+
# نسبة الشموع الخضراء
|
| 634 |
+
green_candles = sum(1 for i in range(1, len(closes)) if closes[i] > opens[i])
|
| 635 |
+
total_candles = len(closes) - 1
|
| 636 |
+
green_ratio = green_candles / total_candles if total_candles > 0 else 0
|
| 637 |
+
|
| 638 |
+
if 0.4 <= green_ratio <= 0.7: # توازن صحي
|
| 639 |
+
efficiency_score = 0.2
|
| 640 |
+
|
| 641 |
+
# قوة الشموع (متوسط طول الجسم)
|
| 642 |
+
candle_bodies = [abs(closes[i] - opens[i]) for i in range(len(closes))]
|
| 643 |
+
avg_body = np.mean(candle_bodies)
|
| 644 |
+
body_ratio = avg_body / current_price * 100
|
| 645 |
+
|
| 646 |
+
if 0.5 <= body_ratio <= 2: # شموع ذات أحجام معقولة
|
| 647 |
+
efficiency_score += 0.2
|
| 648 |
+
|
| 649 |
+
# الحساب النهائي مع الأوزان
|
| 650 |
+
weights = {
|
| 651 |
+
'momentum': 0.30, # الزخم السعري
|
| 652 |
+
'liquidity': 0.25, # السيولة والحجم
|
| 653 |
+
'trend': 0.20, # قوة الاتجاه
|
| 654 |
+
'volatility': 0.10, # التقلب
|
| 655 |
+
'volume_momentum': 0.10, # الزخم الحجمي
|
| 656 |
+
'efficiency': 0.05 # كفاءة الحركة
|
| 657 |
+
}
|
| 658 |
|
| 659 |
final_score = (
|
| 660 |
+
momentum_score * weights['momentum'] +
|
| 661 |
+
liquidity_score * weights['liquidity'] +
|
| 662 |
+
trend_score * weights['trend'] +
|
| 663 |
+
volatility_score * weights['volatility'] +
|
| 664 |
+
volume_momentum_score * weights['volume_momentum'] +
|
| 665 |
+
efficiency_score * weights['efficiency']
|
| 666 |
)
|
| 667 |
|
| 668 |
+
# معامل تصحيح إضافي للرموز ذات الزخم القوي
|
| 669 |
+
if momentum_score > 0.6 and volume_ratio > 1.8:
|
| 670 |
+
final_score = min(final_score * 1.2, 1.0)
|
| 671 |
+
|
| 672 |
+
return min(final_score, 1.0)
|
| 673 |
|
| 674 |
except Exception as e:
|
| 675 |
print(f"❌ خطأ في حساب الدرجة التقنية لـ {symbol}: {e}")
|
| 676 |
return 0.0
|
| 677 |
|
| 678 |
async def find_high_potential_candidates(self, count=20):
|
| 679 |
+
"""
|
| 680 |
+
البحث عن عملات ذات إمكانات عالية بناءً على:
|
| 681 |
+
- الزخم السعري القوي والمستدام
|
| 682 |
+
- السيولة الجيدة (أحجام تداول عالية)
|
| 683 |
+
- اتجاهات تقنية واضحة
|
| 684 |
+
- مؤشرات حجم داعمة
|
| 685 |
+
"""
|
| 686 |
try:
|
| 687 |
+
print(f"🔍 البحث عن {count} عملة ذات إمكانات عالية بناءً على الزخم والسيولة...")
|
| 688 |
|
| 689 |
if not self.exchange:
|
| 690 |
print("❌ لا يوجد اتصال بـ KuCoin")
|
|
|
|
| 693 |
if not self.market_cache or not self.last_market_load or (datetime.now() - self.last_market_load).total_seconds() > 3600:
|
| 694 |
await self._load_markets()
|
| 695 |
|
| 696 |
+
# الحصول على جميع الرموز النشطة مع USDT
|
| 697 |
usdt_symbols = [
|
| 698 |
symbol for symbol in self.market_cache.keys()
|
| 699 |
if symbol.endswith('/USDT') and self.market_cache[symbol].get('active', False)
|
|
|
|
| 701 |
|
| 702 |
print(f"✅ تم العثور على {len(usdt_symbols)} رمز USDT نشط في KuCoin")
|
| 703 |
|
| 704 |
+
# المرحلة 1: الفحص السريع بناءً على بيانات Ticker
|
| 705 |
+
print("📊 المرحلة 1: الفحص السريع بناءً على أحجام التداول والزخم...")
|
| 706 |
+
initial_candidates = []
|
| 707 |
+
|
| 708 |
+
for i, symbol in enumerate(usdt_symbols):
|
| 709 |
+
try:
|
| 710 |
+
# الحصول على بيانات التداول الأساسية
|
| 711 |
+
ticker = await self.exchange.fetch_ticker(symbol)
|
| 712 |
+
if not ticker:
|
| 713 |
+
continue
|
| 714 |
+
|
| 715 |
+
volume_24h = ticker.get('baseVolume', 0)
|
| 716 |
+
price_change_24h = ticker.get('percentage', 0)
|
| 717 |
+
current_price = ticker.get('last', 0)
|
| 718 |
+
|
| 719 |
+
# معايير الفحص الأولي:
|
| 720 |
+
# - حجم تداول > 100,000 دولار
|
| 721 |
+
# - تغير سعري > 1% (إيجابي أو سلبي)
|
| 722 |
+
# - سعر بين 0.001 و 500 دولار (تجنب العملات منخفضة/مرتفعة السعر جداً)
|
| 723 |
+
if (volume_24h * current_price > 100000 and # حجم بالدولار
|
| 724 |
+
abs(price_change_24h) > 1.0 and # زخم سعري
|
| 725 |
+
0.001 <= current_price <= 500): # نطاق سعري معقول
|
| 726 |
+
|
| 727 |
+
reasons = []
|
| 728 |
+
if price_change_24h > 5:
|
| 729 |
+
reasons.append('strong_positive_momentum')
|
| 730 |
+
elif price_change_24h > 2:
|
| 731 |
+
reasons.append('positive_momentum')
|
| 732 |
+
|
| 733 |
+
if volume_24h * current_price > 1000000:
|
| 734 |
+
reasons.append('high_liquidity')
|
| 735 |
+
elif volume_24h * current_price > 500000:
|
| 736 |
+
reasons.append('good_liquidity')
|
| 737 |
+
|
| 738 |
+
initial_candidates.append({
|
| 739 |
+
'symbol': symbol,
|
| 740 |
+
'volume_24h': volume_24h,
|
| 741 |
+
'dollar_volume': volume_24h * current_price,
|
| 742 |
+
'price_change_24h': price_change_24h,
|
| 743 |
+
'current_price': current_price,
|
| 744 |
+
'reasons': reasons
|
| 745 |
+
})
|
| 746 |
+
|
| 747 |
+
# عرض التقدم كل 100 رمز
|
| 748 |
+
if i % 100 == 0 and i > 0:
|
| 749 |
+
print(f" 🔍 تم فحص {i} رمز، وجدنا {len(initial_candidates)} مرشح أولي")
|
| 750 |
+
|
| 751 |
+
except Exception as e:
|
| 752 |
+
if "rate limit" not in str(e).lower():
|
| 753 |
+
continue
|
| 754 |
+
|
| 755 |
+
print(f"✅ انتهت المرحلة 1: تم اختيار {len(initial_candidates)} مرشح أولي")
|
| 756 |
+
|
| 757 |
+
# إذا لم نجد مرشحين كافيين، نخفف المعايير
|
| 758 |
+
if len(initial_candidates) < count * 2:
|
| 759 |
+
print("⚠️ عدد المرشحين قليل، نخفف معايير الفحص...")
|
| 760 |
+
for symbol in usdt_symbols:
|
| 761 |
+
if len(initial_candidates) >= count * 3:
|
| 762 |
+
break
|
| 763 |
+
|
| 764 |
+
if symbol in [c['symbol'] for c in initial_candidates]:
|
| 765 |
+
continue
|
| 766 |
+
|
| 767 |
+
try:
|
| 768 |
+
ticker = await self.exchange.fetch_ticker(symbol)
|
| 769 |
+
if not ticker:
|
| 770 |
+
continue
|
| 771 |
+
|
| 772 |
+
volume_24h = ticker.get('baseVolume', 0)
|
| 773 |
+
price_change_24h = ticker.get('percentage', 0)
|
| 774 |
+
current_price = ticker.get('last', 0)
|
| 775 |
+
|
| 776 |
+
# معايير مخففة
|
| 777 |
+
if (volume_24h * current_price > 50000 and # حجم بالدولار
|
| 778 |
+
abs(price_change_24h) > 0.5 and # زخم سعري
|
| 779 |
+
0.0005 <= current_price <= 1000): # نطاق سعري أوسع
|
| 780 |
+
|
| 781 |
+
reasons = ['relaxed_criteria']
|
| 782 |
+
if price_change_24h > 0:
|
| 783 |
+
reasons.append('positive_trend')
|
| 784 |
+
|
| 785 |
+
initial_candidates.append({
|
| 786 |
+
'symbol': symbol,
|
| 787 |
+
'volume_24h': volume_24h,
|
| 788 |
+
'dollar_volume': volume_24h * current_price,
|
| 789 |
+
'price_change_24h': price_change_24h,
|
| 790 |
+
'current_price': current_price,
|
| 791 |
+
'reasons': reasons
|
| 792 |
+
})
|
| 793 |
+
|
| 794 |
+
except Exception:
|
| 795 |
+
continue
|
| 796 |
+
|
| 797 |
+
print(f"📊 بعد التخفيف: {len(initial_candidates)} مرشح")
|
| 798 |
+
|
| 799 |
+
# المرحلة 2: التحليل التقني المتعمق
|
| 800 |
+
print("📈 المرحلة 2: التحليل التقني المتعمق للمرشحين...")
|
| 801 |
candidates_with_scores = []
|
| 802 |
analyzed_count = 0
|
| 803 |
|
| 804 |
+
# نرتب المرشحين بحجم التداول (الأكبر أولاً)
|
| 805 |
+
initial_candidates.sort(key=lambda x: x['dollar_volume'], reverse=True)
|
| 806 |
+
|
| 807 |
+
for candidate in initial_candidates[:100]: # نأخذ أفضل 100 من حيث الحجم
|
| 808 |
try:
|
| 809 |
+
symbol = candidate['symbol']
|
| 810 |
analyzed_count += 1
|
| 811 |
+
|
| 812 |
if analyzed_count % 10 == 0:
|
| 813 |
+
print(f" 🔍 تحليل تقني {analyzed_count} من {min(100, len(initial_candidates))}")
|
| 814 |
|
| 815 |
+
# جلب بيانات OHLCV للتحليل التقني
|
| 816 |
+
ohlcv_1h = await self.exchange.fetch_ohlcv(symbol, '1h', limit=100)
|
| 817 |
|
| 818 |
+
if not ohlcv_1h or len(ohlcv_1h) < 50:
|
| 819 |
continue
|
| 820 |
|
| 821 |
ohlcv_data = {'1h': ohlcv_1h}
|
| 822 |
|
| 823 |
+
# حساب الدرجة التقنية الشاملة
|
| 824 |
technical_score = await self._calculate_technical_score(symbol, ohlcv_data)
|
| 825 |
|
| 826 |
+
if technical_score > 0.3: # عتبة مقبولة للجودة
|
| 827 |
+
# تحديث أسباب الترشيح بناءً على التحليل التقني
|
| 828 |
+
reasons = candidate['reasons']
|
| 829 |
|
|
|
|
| 830 |
if technical_score > 0.7:
|
| 831 |
+
reasons.append('excellent_technical_score')
|
| 832 |
+
elif technical_score > 0.5:
|
| 833 |
+
reasons.append('good_technical_score')
|
| 834 |
+
|
| 835 |
+
# إضافة أسباب تقنية
|
| 836 |
+
if candidate['price_change_24h'] > 8:
|
| 837 |
+
reasons.append('strong_daily_momentum')
|
| 838 |
+
elif candidate['price_change_24h'] > 3:
|
| 839 |
+
reasons.append('moderate_daily_momentum')
|
| 840 |
+
|
| 841 |
+
if candidate['dollar_volume'] > 2000000:
|
| 842 |
+
reasons.append('very_high_liquidity')
|
| 843 |
+
elif candidate['dollar_volume'] > 500000:
|
| 844 |
reasons.append('high_liquidity')
|
|
|
|
|
|
|
| 845 |
|
| 846 |
candidates_with_scores.append({
|
| 847 |
'symbol': symbol,
|
| 848 |
'technical_score': technical_score,
|
| 849 |
'reasons': reasons,
|
| 850 |
+
'volume': candidate['volume_24h'],
|
| 851 |
+
'dollar_volume': candidate['dollar_volume'],
|
| 852 |
+
'price_change': candidate['price_change_24h'],
|
| 853 |
+
'current_price': candidate['current_price'],
|
| 854 |
+
'market_cap_category': self._classify_market_cap(candidate['dollar_volume'])
|
| 855 |
})
|
| 856 |
|
| 857 |
except Exception as e:
|
| 858 |
if "rate limit" not in str(e).lower():
|
| 859 |
+
print(f"⚠️ خطأ في التحليل التقني للرمز {symbol}: {e}")
|
| 860 |
continue
|
| 861 |
|
| 862 |
+
print(f"✅ انتهت المرحلة 2: {len(candidates_with_scores)} مرشح اجتازوا التحليل التقني")
|
| 863 |
+
|
| 864 |
+
# المرحلة 3: الاختيار النهائي مع التنوع
|
| 865 |
+
if not candidates_with_scores:
|
| 866 |
+
print("❌ لم يتم العثور على أي مرشح يلبي المعايير")
|
| 867 |
+
return []
|
| 868 |
+
|
| 869 |
+
# ترتيب المرشحين حسب الدرجة التقنية
|
| 870 |
candidates_with_scores.sort(key=lambda x: x['technical_score'], reverse=True)
|
|
|
|
| 871 |
|
| 872 |
+
# نأخذ أفضل المرشحين مع الحفاظ على التنوع
|
| 873 |
+
final_candidates = []
|
| 874 |
+
categories_count = {'LARGE': 0, 'MID': 0, 'SMALL': 0}
|
| 875 |
+
max_per_category = max(1, count // 3)
|
| 876 |
+
|
| 877 |
+
for candidate in candidates_with_scores:
|
| 878 |
+
category = candidate['market_cap_category']
|
| 879 |
+
|
| 880 |
+
if categories_count[category] < max_per_category:
|
| 881 |
+
final_candidates.append(candidate)
|
| 882 |
+
categories_count[category] += 1
|
| 883 |
+
elif len(final_candidates) < count:
|
| 884 |
+
final_candidates.append(candidate)
|
| 885 |
+
|
| 886 |
+
if len(final_candidates) >= count:
|
| 887 |
+
break
|
| 888 |
|
| 889 |
+
print(f"🎯 الاختيار النهائي: {len(final_candidates)} مرشح متنوع")
|
| 890 |
+
print(f" 📊 تصنيف الأحجام: Large({categories_count['LARGE']}), Mid({categories_count['MID']}), Small({categories_count['SMALL']})")
|
| 891 |
|
| 892 |
+
# عرض أفضل 5 مرشحين
|
| 893 |
+
for i, candidate in enumerate(final_candidates[:5]):
|
| 894 |
+
print(f" 🥇 {i+1}. {candidate['symbol']}: درجة {candidate['technical_score']:.3f} - تغير {candidate['price_change']:.1f}% - حجم ${candidate['dollar_volume']:,.0f}")
|
| 895 |
+
|
| 896 |
+
return final_candidates
|
| 897 |
|
| 898 |
except Exception as e:
|
| 899 |
print(f"❌ خطأ في find_high_potential_candidates: {e}")
|
| 900 |
+
import traceback
|
| 901 |
+
traceback.print_exc()
|
| 902 |
return []
|
| 903 |
|
| 904 |
+
def _classify_market_cap(self, dollar_volume):
|
| 905 |
+
"""تصنيف العملات حسب حجم التداول"""
|
| 906 |
+
if dollar_volume > 5000000: # أكثر من 5 ملايين دولار
|
| 907 |
+
return "LARGE"
|
| 908 |
+
elif dollar_volume > 1000000: # أكثر من 1 مليون دولار
|
| 909 |
+
return "MID"
|
| 910 |
+
else: # أقل من 1 مليون دولار
|
| 911 |
+
return "SMALL"
|
| 912 |
+
|
| 913 |
async def get_fast_pass_data_async(self, candidates):
|
| 914 |
try:
|
| 915 |
print(f"📊 جلب بيانات OHLCV لـ {len(candidates)} مرشح من KuCoin...")
|