Spaces:
Running
Running
Update whale_news_data.py
Browse files- whale_news_data.py +150 -183
whale_news_data.py
CHANGED
|
@@ -124,7 +124,7 @@ class EnhancedWhaleMonitor:
|
|
| 124 |
'chain_id': 'mainnet-beta',
|
| 125 |
'native_coin': 'SOL',
|
| 126 |
'explorer': 'solscan',
|
| 127 |
-
'rpc_endpoints': self._get_solana_rpc_endpoints(),
|
| 128 |
'type': 'solana'
|
| 129 |
}
|
| 130 |
}
|
|
@@ -294,24 +294,18 @@ class EnhancedWhaleMonitor:
|
|
| 294 |
'https://rpc.ankr.com/fantom'
|
| 295 |
]
|
| 296 |
|
| 297 |
-
# ✅✅✅ --- الدالة المحدثة لقائمة Solana RPC (بدون مفاتيح API) --- ✅✅✅
|
| 298 |
def _get_solana_rpc_endpoints(self):
|
| 299 |
"""الحصول على نقاط RPC مجانية لشبكة Solana (بدون مفاتيح API)"""
|
| 300 |
endpoints = [
|
| 301 |
-
'https://api.mainnet-beta.solana.com',
|
| 302 |
-
'https://ssc-dao.genesysgo.net/',
|
| 303 |
-
'https://solana-mainnet.rpc.extrnode.com',
|
| 304 |
-
'https://solana-mainnet.phantom.tech/',
|
| 305 |
-
'https://mainnet.rpc.solana.pyth.network',
|
| 306 |
-
|
| 307 |
-
# --- نقاط عامة أخرى يمكن تجربتها إذا لزم الأمر ---
|
| 308 |
-
# 'https://solana.public-rpc.com' # سبب مشاكل SSL سابقاً
|
| 309 |
]
|
| 310 |
-
# إضافة Ankr في النهاية
|
| 311 |
-
endpoints.append('https://rpc.ankr.com/solana')
|
| 312 |
print(f"ℹ️ قائمة Solana RPC Endpoints (مجانية وبدون مفاتيح) المستخدمة: {endpoints}")
|
| 313 |
return endpoints
|
| 314 |
-
# ✅✅✅ --- نهاية الدالة المحدثة --- ✅✅✅
|
| 315 |
|
| 316 |
def _initialize_comprehensive_exchange_addresses(self):
|
| 317 |
"""تهيئة قاعدة بيانات شاملة لعناوين كل المنصات"""
|
|
@@ -336,11 +330,10 @@ class EnhancedWhaleMonitor:
|
|
| 336 |
'gate': [ '0x0d0707963952f2fba59dd06f2b425ace40b492fe', '0xc6f9741f5a5a676b91171804cf3c500ab438bb6e' ], # إضافة Gate.io
|
| 337 |
'uniswap': [ '0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad', '0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45' ],
|
| 338 |
'pancakeswap': [ '0x10ed43c718714eb63d5aa57b78b54704e256024e', '0x13f4ea83d0bd40e75c8222255bc855a974568dd4' ],
|
| 339 |
-
# عناوين Solana (تحقق من صحتها وتحديثها إذا لزم الأمر)
|
| 340 |
'solana_kucoin': ['F3a5ZLPKUCrCj6CGKP2m9wS9Y2L8RcUBoJq9L2D2sUYi', '2AQdpHJ2JpcEgPiATFnAKfV9hPMvouWJAhKvamQ2Krqy' ],
|
| 341 |
'solana_binance': ['5tzFkiKscXHK5ZXCGbXZJpXaWuBtZ6RrH8eL2a7Yi7Vn', '9WzDXwBbmkg8ZTbNMqUxvQRApF22xwsgMj2SUZrchK2E'],
|
| 342 |
-
'solana_coinbase': ['CeBiLnAE3UAW9mCDSjD1VULKLeQjefjN4d941fVKazSM'],
|
| 343 |
-
'solana_wormhole': ['worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth']
|
| 344 |
}
|
| 345 |
|
| 346 |
self.address_categories = {'exchange': set(), 'cex': set(), 'dex': set(), 'bridge': set(), 'whale': set(), 'unknown': set()}
|
|
@@ -352,7 +345,6 @@ class EnhancedWhaleMonitor:
|
|
| 352 |
self.address_labels[addr_lower] = category
|
| 353 |
self.address_categories['exchange'].add(addr_lower)
|
| 354 |
|
| 355 |
-
# تصنيف أدق
|
| 356 |
if category in ['kucoin', 'binance', 'coinbase', 'kraken', 'okx', 'gate', 'solana_kucoin', 'solana_binance', 'solana_coinbase']:
|
| 357 |
self.address_categories['cex'].add(addr_lower)
|
| 358 |
elif category in ['uniswap', 'pancakeswap']:
|
|
@@ -581,84 +573,85 @@ class EnhancedWhaleMonitor:
|
|
| 581 |
endpoint_name = endpoint.split('//')[1].split('/')[0] if '//' in endpoint else endpoint
|
| 582 |
print(f" 🔍 محاولة جلب سجلات التحويلات لـ {network} عبر {endpoint_name}...")
|
| 583 |
|
| 584 |
-
async with self.http_client
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
if not logs:
|
| 625 |
-
print(f" ✅ لا توجد سجلات تحويل لـ {contract_address} في آخر {blocks_to_scan} كتل عبر {endpoint_name}")
|
| 626 |
-
successful_endpoint = endpoint_name
|
| 627 |
-
break
|
| 628 |
-
|
| 629 |
-
print(f" 📊 تم العثور على {len(logs)} سجل تحويل محتمل.")
|
| 630 |
-
|
| 631 |
-
for log in logs:
|
| 632 |
-
try:
|
| 633 |
-
topics = log.get('topics', [])
|
| 634 |
-
data = log.get('data', '0x')
|
| 635 |
-
block_num_hex = log.get('blockNumber')
|
| 636 |
-
tx_hash = log.get('transactionHash')
|
| 637 |
-
log_index_hex = log.get('logIndex', '0x0')
|
| 638 |
-
|
| 639 |
-
if len(topics) == 3 and data != '0x' and block_num_hex and tx_hash:
|
| 640 |
-
sender = '0x' + topics[1][26:]
|
| 641 |
-
receiver = '0x' + topics[2][26:]
|
| 642 |
-
value = str(int(data, 16))
|
| 643 |
-
block_num = str(int(block_num_hex, 16))
|
| 644 |
-
log_index = str(int(log_index_hex, 16))
|
| 645 |
-
|
| 646 |
-
transfers.append({
|
| 647 |
-
'hash': tx_hash, 'from': sender.lower(), 'to': receiver.lower(),
|
| 648 |
-
'value': value, 'timeStamp': str(latest_block_timestamp_approx),
|
| 649 |
-
'blockNumber': block_num, 'network': network, 'logIndex': log_index
|
| 650 |
-
})
|
| 651 |
-
else:
|
| 652 |
-
print(f" ⚠️ سجل غير متوافق مع Transfer Event: Topics={len(topics)}, Data={data[:10]}...")
|
| 653 |
-
|
| 654 |
-
except (ValueError, TypeError, KeyError, IndexError) as log_parse_error:
|
| 655 |
-
print(f" ⚠️ خطأ في تحليل سجل فردي: {log_parse_error} - Log: {log}")
|
| 656 |
-
continue
|
| 657 |
|
| 658 |
-
|
|
|
|
| 659 |
successful_endpoint = endpoint_name
|
| 660 |
break
|
| 661 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 662 |
except ssl.SSLCertVerificationError as ssl_err:
|
| 663 |
print(f" ❌ خطأ SSL مع {endpoint_name}: {ssl_err}")
|
| 664 |
continue
|
|
@@ -672,6 +665,11 @@ class EnhancedWhaleMonitor:
|
|
| 672 |
print(f" ❌ خطأ في تحليل استجابة JSON من {endpoint_name}: {json_err}")
|
| 673 |
continue
|
| 674 |
except Exception as e:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 675 |
print(f" ❌ فشل غير متوقع مع {endpoint_name}: {e}")
|
| 676 |
traceback.print_exc()
|
| 677 |
continue
|
|
@@ -711,8 +709,8 @@ class EnhancedWhaleMonitor:
|
|
| 711 |
"params": [ token_address, {"limit": limit, "commitment": "confirmed"} ]
|
| 712 |
}
|
| 713 |
|
| 714 |
-
async with self.http_client
|
| 715 |
-
|
| 716 |
|
| 717 |
if response_signatures.status_code == 403:
|
| 718 |
print(f" ⚠️ Solana endpoint {endpoint_name} failed: 403 Forbidden")
|
|
@@ -746,12 +744,12 @@ class EnhancedWhaleMonitor:
|
|
| 746 |
|
| 747 |
processed_count = 0
|
| 748 |
transaction_tasks = []
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
| 755 |
|
| 756 |
for detail_result in transaction_details:
|
| 757 |
if detail_result and self._is_solana_token_transfer(detail_result, token_address):
|
|
@@ -777,6 +775,10 @@ class EnhancedWhaleMonitor:
|
|
| 777 |
print(f" ❌ خطأ في تحليل استجابة JSON من Solana ({endpoint_name}): {json_err}")
|
| 778 |
continue
|
| 779 |
except Exception as e:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 780 |
print(f" ❌ فشل غير متوقع مع endpoint Solana ({endpoint_name}): {e}")
|
| 781 |
traceback.print_exc()
|
| 782 |
continue
|
|
@@ -860,7 +862,7 @@ class EnhancedWhaleMonitor:
|
|
| 860 |
print(f" ❌ خطأ SSL في getTransaction لـ {signature[:10]}... : {ssl_err}")
|
| 861 |
raise ssl_err
|
| 862 |
except json.JSONDecodeError as json_err:
|
| 863 |
-
print(f" ❌ خطأ JSON في getTransaction لـ {signature[:10]}... : {
|
| 864 |
return None
|
| 865 |
except Exception as e:
|
| 866 |
print(f" ❌ استثناء غير متوقع في getTransaction لـ {signature[:10]}... : {e}")
|
|
@@ -890,18 +892,16 @@ class EnhancedWhaleMonitor:
|
|
| 890 |
all_balances = (post_balances or []) + (pre_balances or [])
|
| 891 |
for balance in all_balances:
|
| 892 |
if isinstance(balance, dict) and balance.get('mint') == token_address:
|
| 893 |
-
# التحقق من وجود تغيير فعلي في الرصيد الخام
|
| 894 |
pre_amount = next((pb.get('uiTokenAmount',{}).get('amount','0') for pb in (pre_balances or []) if isinstance(pb, dict) and pb.get('owner') == balance.get('owner') and pb.get('mint') == token_address), '0')
|
| 895 |
post_amount = balance.get('uiTokenAmount',{}).get('amount','0')
|
| 896 |
try:
|
| 897 |
if int(post_amount) != int(pre_amount):
|
| 898 |
return True
|
| 899 |
except (ValueError, TypeError):
|
| 900 |
-
pass
|
| 901 |
|
| 902 |
return False
|
| 903 |
except Exception as e:
|
| 904 |
-
# print(f" ⚠️ خطأ في التحقق من نوع معاملة Solana: {e}")
|
| 905 |
return False
|
| 906 |
|
| 907 |
async def _parse_solana_transfer(self, transaction, token_address):
|
|
@@ -943,11 +943,9 @@ class EnhancedWhaleMonitor:
|
|
| 943 |
amount_raw = diff_raw
|
| 944 |
elif diff_raw < 0:
|
| 945 |
sender = owner
|
| 946 |
-
# لا نعيد التعيين، يجب أن يتطابق الفرق
|
| 947 |
|
| 948 |
except (ValueError, TypeError):
|
| 949 |
print(f" ⚠️ تحذير: فشل تحليل القيمة الخام لـ Solana ({owner}).")
|
| 950 |
-
# محاولة استخدام uiAmount كبديل (أقل دقة)
|
| 951 |
pre_ui = pre_bal_data.get('uiAmount', 0.0) or 0.0
|
| 952 |
post_ui = post_bal_data.get('uiAmount', 0.0) or 0.0
|
| 953 |
diff_ui = post_ui - pre_ui
|
|
@@ -959,7 +957,6 @@ class EnhancedWhaleMonitor:
|
|
| 959 |
elif diff_ui < -1e-9:
|
| 960 |
sender = owner
|
| 961 |
|
| 962 |
-
# إذا لم نجد من الأرصدة، تحقق من التعليمات الداخلية
|
| 963 |
if not sender or not receiver or amount_raw == 0:
|
| 964 |
inner_instructions = meta.get('innerInstructions', [])
|
| 965 |
for inner_inst_set in inner_instructions:
|
|
@@ -967,23 +964,20 @@ class EnhancedWhaleMonitor:
|
|
| 967 |
parsed = inst.get('parsed')
|
| 968 |
if isinstance(parsed, dict) and parsed.get('type') in ['transfer', 'transferChecked'] and parsed.get('info', {}).get('mint') == token_address:
|
| 969 |
info = parsed.get('info', {})
|
| 970 |
-
sender_inner = info.get('source') or info.get('authority')
|
| 971 |
receiver_inner = info.get('destination')
|
| 972 |
-
amount_inner_str = info.get('tokenAmount', {}).get('amount') or info.get('amount')
|
| 973 |
try:
|
| 974 |
amount_inner_raw = int(amount_inner_str)
|
| 975 |
if sender_inner and receiver_inner and amount_inner_raw > 0:
|
| 976 |
sender = sender_inner
|
| 977 |
receiver = receiver_inner
|
| 978 |
amount_raw = amount_inner_raw
|
| 979 |
-
# الخروج من الحلقات بمجرد العثور على تحويل داخلي صالح
|
| 980 |
break
|
| 981 |
except (ValueError, TypeError): continue
|
| 982 |
-
if sender and receiver and amount_raw > 0: break
|
| 983 |
-
if sender and receiver and amount_raw > 0: break
|
| 984 |
-
|
| 985 |
|
| 986 |
-
# التحقق النهائي
|
| 987 |
if sender and receiver and amount_raw > 0:
|
| 988 |
return {
|
| 989 |
'hash': signature, 'from': sender, 'to': receiver,
|
|
@@ -992,7 +986,6 @@ class EnhancedWhaleMonitor:
|
|
| 992 |
'type': 'solana_transfer', 'logIndex': '0'
|
| 993 |
}
|
| 994 |
else:
|
| 995 |
-
# print(f" ℹ️ معاملة Solana {signature[:10]}... لم يتم التعرف عليها كتحويل صالح.")
|
| 996 |
return None
|
| 997 |
|
| 998 |
except Exception as e:
|
|
@@ -1012,30 +1005,22 @@ class EnhancedWhaleMonitor:
|
|
| 1012 |
'ethereum': {'url': 'https://api.etherscan.io/api', 'key': self.etherscan_key},
|
| 1013 |
'bsc': {'url': 'https://api.bscscan.com/api', 'key': self.bscscan_key},
|
| 1014 |
'polygon': {'url': 'https://api.polygonscan.com/api', 'key': self.polygonscan_key}
|
| 1015 |
-
# يمكن إضافة المزيد هنا للمستكشفات الأخرى إذا كان لديك مفاتيح API لها
|
| 1016 |
}
|
| 1017 |
|
| 1018 |
if network not in explorer_config or not explorer_config[network].get('key'):
|
| 1019 |
-
# print(f"⚠️ لا يوجد مستكشف مدعوم أو مفتاح API لشبكة {network}")
|
| 1020 |
return []
|
| 1021 |
|
| 1022 |
config = explorer_config[network]
|
| 1023 |
-
|
| 1024 |
-
address_param = "contractaddress" if network == 'ethereum' else "address" # BscScan و PolygonScan يستخدمان address
|
| 1025 |
|
| 1026 |
params = {
|
| 1027 |
-
"module": "account",
|
| 1028 |
-
|
| 1029 |
-
|
| 1030 |
-
"
|
| 1031 |
-
"offset": 100,
|
| 1032 |
-
"startblock": 0,
|
| 1033 |
-
"endblock": 999999999,
|
| 1034 |
-
"sort": "desc",
|
| 1035 |
-
"apikey": config['key']
|
| 1036 |
}
|
| 1037 |
|
| 1038 |
-
async with httpx.AsyncClient(timeout=20.0) as client:
|
| 1039 |
response = await client.get(config['url'], params=params)
|
| 1040 |
|
| 1041 |
if response.status_code == 200:
|
|
@@ -1047,13 +1032,11 @@ class EnhancedWhaleMonitor:
|
|
| 1047 |
transfers = data.get('result', [])
|
| 1048 |
processed_transfers = []
|
| 1049 |
for tf in transfers:
|
| 1050 |
-
# فلترة إضافية للتأكد من أنه توكن ERC20 وليس Native Coin Transfer
|
| 1051 |
-
# وأنه للعقد الصحيح (بعض APIs قد تعيد نتائج أوسع)
|
| 1052 |
if tf.get('tokenSymbol') and tf.get('contractAddress','').lower() == contract_address.lower():
|
| 1053 |
tf['network'] = network
|
| 1054 |
if 'from' in tf: tf['from'] = tf['from'].lower()
|
| 1055 |
if 'to' in tf: tf['to'] = tf['to'].lower()
|
| 1056 |
-
if 'logIndex' not in tf: tf['logIndex'] = 'N/A'
|
| 1057 |
processed_transfers.append(tf)
|
| 1058 |
|
| 1059 |
print(f" ✅ Explorer {network}: تم جلب {len(processed_transfers)} تحويلة توكن للعقد المحدد.")
|
|
@@ -1231,6 +1214,7 @@ class EnhancedWhaleMonitor:
|
|
| 1231 |
'llm_friendly_summary': llm_summary
|
| 1232 |
}
|
| 1233 |
|
|
|
|
| 1234 |
async def _get_accurate_token_value_optimized(self, raw_value, decimals, symbol):
|
| 1235 |
"""حساب القيمة بالدولار باستخدام decimals مُمررة والسعر الحالي"""
|
| 1236 |
try:
|
|
@@ -1276,8 +1260,8 @@ class EnhancedWhaleMonitor:
|
|
| 1276 |
"params": [{"to": contract_address, "data": "0x313ce567"}, "latest"],
|
| 1277 |
"id": int(time.time())
|
| 1278 |
}
|
| 1279 |
-
|
| 1280 |
-
|
| 1281 |
if response.status_code == 200:
|
| 1282 |
result = response.json().get('result')
|
| 1283 |
if result and result != '0x' and result.startswith('0x'):
|
|
@@ -1291,18 +1275,20 @@ class EnhancedWhaleMonitor:
|
|
| 1291 |
print(f" ⚠️ فشل تحويل نتيجة decimals من RPC ({result}) لـ {contract_address} على {network}")
|
| 1292 |
continue
|
| 1293 |
except Exception as rpc_decimal_error:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1294 |
print(f" ⚠️ خطأ RPC أثناء جلب decimals من {endpoint.split('//')[-1]}: {rpc_decimal_error}")
|
| 1295 |
continue
|
| 1296 |
|
| 1297 |
elif network == 'solana':
|
| 1298 |
print(f"ℹ️ جلب decimals لـ Solana ({contract_address}) غير مدعوم حاليًا عبر RPC.")
|
| 1299 |
-
|
| 1300 |
-
estimated_decimals = 9 # أو 6 هي قيم شائعة أخرى
|
| 1301 |
self.token_decimals_cache[cache_key] = estimated_decimals
|
| 1302 |
print(f" ⚠️ استخدام قيمة decimals تقديرية لـ Solana: {estimated_decimals}")
|
| 1303 |
return estimated_decimals
|
| 1304 |
|
| 1305 |
-
|
| 1306 |
print(f"❌ فشل جلب الكسور العشرية للعقد {contract_address} على شبكة {network} من جميع المصادر.")
|
| 1307 |
return None
|
| 1308 |
|
|
@@ -1319,13 +1305,11 @@ class EnhancedWhaleMonitor:
|
|
| 1319 |
if cache_entry and time.time() - cache_entry.get('timestamp', 0) < 300:
|
| 1320 |
return cache_entry['price']
|
| 1321 |
|
| 1322 |
-
# المحاولة 1: CoinGecko API
|
| 1323 |
price = await self._get_token_price_from_coingecko(symbol)
|
| 1324 |
if price is not None and price > 0:
|
| 1325 |
self.token_price_cache[base_symbol] = {'price': price, 'timestamp': time.time()}
|
| 1326 |
return price
|
| 1327 |
|
| 1328 |
-
# المحاولة 2: KuCoin API (ccxt.pro)
|
| 1329 |
price = await self._get_token_price_from_kucoin(symbol)
|
| 1330 |
if price is not None and price > 0:
|
| 1331 |
self.token_price_cache[base_symbol] = {'price': price, 'timestamp': time.time()}
|
|
@@ -1349,7 +1333,6 @@ class EnhancedWhaleMonitor:
|
|
| 1349 |
'LTC': 'litecoin', 'XLM': 'stellar', 'TRX': 'tron', 'EOS': 'eos', 'XMR': 'monero',
|
| 1350 |
'SOL': 'solana', 'MATIC': 'matic-network', 'AVAX': 'avalanche-2', 'LINK': 'chainlink',
|
| 1351 |
'ATOM': 'cosmos', 'UNI': 'uniswap', 'AAVE': 'aave', 'KCS': 'kucoin-shares',
|
| 1352 |
-
# يجب إضافة المزيد من العملات هنا لزيادة التغطية
|
| 1353 |
'MAVIA': 'heroes-of-mavia', 'COMMON': 'commonwealth', 'WLFI': 'wolfi',
|
| 1354 |
'PINGPONG': 'pingpong', 'YB': 'yourbusd', 'REACT': 'react', 'XMN': 'xmine',
|
| 1355 |
'ANOME': 'anome', 'ZEN': 'zencash', 'AKT': 'akash-network', 'UB': 'unibit'
|
|
@@ -1358,7 +1341,7 @@ class EnhancedWhaleMonitor:
|
|
| 1358 |
base_symbol = symbol.split('/')[0].upper()
|
| 1359 |
coingecko_id = symbol_mapping.get(base_symbol)
|
| 1360 |
if not coingecko_id:
|
| 1361 |
-
coingecko_id = base_symbol.lower()
|
| 1362 |
print(f"ℹ️ رمز {base_symbol} غير موجود في symbol_mapping، استخدام {coingecko_id} لـ CoinGecko.")
|
| 1363 |
|
| 1364 |
|
|
@@ -1368,6 +1351,7 @@ class EnhancedWhaleMonitor:
|
|
| 1368 |
max_retries = 2
|
| 1369 |
for attempt in range(max_retries):
|
| 1370 |
async with self.coingecko_semaphore:
|
|
|
|
| 1371 |
async with httpx.AsyncClient(timeout=10.0, headers=headers, verify=self.ssl_context) as client:
|
| 1372 |
try:
|
| 1373 |
response = await client.get(url)
|
|
@@ -1388,25 +1372,22 @@ class EnhancedWhaleMonitor:
|
|
| 1388 |
print(f"⚠️ استجابة CoinGecko تحتوي على سعر غير صالح لـ {symbol}: {price}")
|
| 1389 |
return 0
|
| 1390 |
else:
|
| 1391 |
-
# محاولة البحث عن المعرف الصحيح إذا فشلت المحاولة الأولى
|
| 1392 |
if attempt == 0:
|
| 1393 |
print(f"⚠️ لم يتم العثور على سعر لـ {coingecko_id} في CoinGecko، محاولة البحث عن المعرف...")
|
| 1394 |
-
await asyncio.sleep(1.1)
|
| 1395 |
found_id = await self._find_coingecko_id_via_search(base_symbol, client)
|
| 1396 |
if found_id and found_id != coingecko_id:
|
| 1397 |
print(f" 🔄 تم العثور على معرف بديل: {found_id}. إعادة المحاولة...")
|
| 1398 |
-
coingecko_id = found_id
|
| 1399 |
-
# تعديل URL
|
| 1400 |
url = f"https://api.coingecko.com/api/v3/simple/price?ids={coingecko_id}&vs_currencies=usd"
|
| 1401 |
-
continue
|
| 1402 |
else:
|
| 1403 |
print(f" ❌ لم يتم العثور على معرف بديل لـ {base_symbol}.")
|
| 1404 |
-
return 0
|
| 1405 |
-
else:
|
| 1406 |
print(f"⚠️ لم يتم العثور على سعر لـ {coingecko_id} في استجابة CoinGecko بعد البحث.")
|
| 1407 |
return 0
|
| 1408 |
|
| 1409 |
-
# --- معالجة الأخطاء تبقى كما هي ---
|
| 1410 |
except httpx.HTTPStatusError as http_err:
|
| 1411 |
print(f"❌ خطأ HTTP من CoinGecko لـ {symbol}: {http_err.response.status_code}")
|
| 1412 |
return 0
|
|
@@ -1424,8 +1405,8 @@ class EnhancedWhaleMonitor:
|
|
| 1424 |
print(f"❌ فشل عام في _get_token_price_from_coingecko لـ {symbol}: {e}")
|
| 1425 |
return 0
|
| 1426 |
|
| 1427 |
-
# دالة مساعدة للبحث عن معرف CoinGecko
|
| 1428 |
async def _find_coingecko_id_via_search(self, symbol, client: httpx.AsyncClient):
|
|
|
|
| 1429 |
try:
|
| 1430 |
search_url = f"https://api.coingecko.com/api/v3/search?query={symbol}"
|
| 1431 |
response = await client.get(search_url)
|
|
@@ -1433,12 +1414,10 @@ class EnhancedWhaleMonitor:
|
|
| 1433 |
data = response.json()
|
| 1434 |
coins = data.get('coins', [])
|
| 1435 |
if coins:
|
| 1436 |
-
# البحث عن تطابق الرمز أولاً
|
| 1437 |
search_symbol_lower = symbol.lower()
|
| 1438 |
for coin in coins:
|
| 1439 |
if coin.get('symbol', '').lower() == search_symbol_lower:
|
| 1440 |
return coin.get('id')
|
| 1441 |
-
# إذا لم نجد، نأخذ معرف أول نتيجة
|
| 1442 |
return coins[0].get('id')
|
| 1443 |
return None
|
| 1444 |
except Exception as e:
|
|
@@ -1450,12 +1429,11 @@ class EnhancedWhaleMonitor:
|
|
| 1450 |
"""جلب سعر العملة من KuCoin كبديل (بشكل غير متزامن)"""
|
| 1451 |
exchange = None
|
| 1452 |
try:
|
| 1453 |
-
# استخدام enableRateLimit مهم لـ ccxt.pro
|
| 1454 |
exchange = ccxt.kucoin({'enableRateLimit': True})
|
| 1455 |
-
# التحقق من أن السوق موجود قبل محاولة جلبه
|
| 1456 |
markets = await exchange.load_markets()
|
| 1457 |
if symbol not in markets:
|
| 1458 |
print(f"⚠️ الرمز {symbol} غير موجود في أسواق KuCoin.")
|
|
|
|
| 1459 |
return 0
|
| 1460 |
|
| 1461 |
ticker = await exchange.fetch_ticker(symbol)
|
|
@@ -1480,7 +1458,6 @@ class EnhancedWhaleMonitor:
|
|
| 1480 |
print(f"❌ خطأ شبكة أثناء جلب السعر من KuCoin لـ {symbol}: {e}")
|
| 1481 |
return 0
|
| 1482 |
except ccxt.ExchangeError as e:
|
| 1483 |
-
# تحسين طباعة الخطأ
|
| 1484 |
print(f"❌ خطأ منصة KuCoin ({type(e).__name__}) لـ {symbol}: {e}")
|
| 1485 |
return 0
|
| 1486 |
except Exception as e:
|
|
@@ -1500,50 +1477,42 @@ class EnhancedWhaleMonitor:
|
|
| 1500 |
abs_net_flow = abs(net_flow_usd)
|
| 1501 |
total_flow_volume = total_to_exchanges_usd + total_from_exchanges_usd
|
| 1502 |
|
| 1503 |
-
# مضاعف الثقة يعتمد بشكل أكبر على الحجم المطلق للتدفق وعدد التحويلات
|
| 1504 |
confidence_multiplier = min( (abs_net_flow / 500000.0) + (whale_transfers_count / 5.0) , 1.5)
|
| 1505 |
-
|
| 1506 |
base_confidence = 0.5 + (network_strength * 0.1)
|
| 1507 |
|
| 1508 |
action = 'HOLD'
|
| 1509 |
reason = f'نشاط حيتان عبر {len(network_stats)} شبكة: {whale_transfers_count} تحويلة كبيرة بقيمة إجمالية ${total_flow_volume:,.0f}. صافي التدفق ${net_flow_usd:,.0f}'
|
| 1510 |
critical_alert = False
|
| 1511 |
|
| 1512 |
-
|
| 1513 |
-
if net_flow_usd > 500000 and deposit_count >= 2: # قوي
|
| 1514 |
action = 'STRONG_SELL'
|
| 1515 |
confidence = min(0.7 + (base_confidence * confidence_multiplier * 0.3), 0.95)
|
| 1516 |
reason = f'ضغط بيعي قوي عبر {len(network_stats)} شبكة: ${net_flow_usd:,.0f} إيداع للمنصات ({deposit_count} تحويلة).'
|
| 1517 |
critical_alert = abs_net_flow > 1000000
|
| 1518 |
-
elif net_flow_usd > 150000 and (deposit_count >= 1 or whale_transfers_count > 0):
|
| 1519 |
action = 'SELL'
|
| 1520 |
confidence = min(0.6 + (base_confidence * confidence_multiplier * 0.2), 0.85)
|
| 1521 |
reason = f'ضغط بيعي محتمل عبر {len(network_stats)} شبكة: ${net_flow_usd:,.0f} إيداع للمنصات.'
|
| 1522 |
critical_alert = abs_net_flow > 750000
|
| 1523 |
-
|
| 1524 |
-
# شروط الشراء
|
| 1525 |
-
elif net_flow_usd < -500000 and withdrawal_count >= 2: # قوي
|
| 1526 |
action = 'STRONG_BUY'
|
| 1527 |
confidence = min(0.7 + (base_confidence * confidence_multiplier * 0.3), 0.95)
|
| 1528 |
reason = f'تراكم شرائي قوي عبر {len(network_stats)} شبكة: ${abs_net_flow:,.0f} سحب من المنصات ({withdrawal_count} تحويلة).'
|
| 1529 |
critical_alert = abs_net_flow > 1000000
|
| 1530 |
-
elif net_flow_usd < -150000 and (withdrawal_count >= 1 or whale_transfers_count > 0):
|
| 1531 |
action = 'BUY'
|
| 1532 |
confidence = min(0.6 + (base_confidence * confidence_multiplier * 0.2), 0.85)
|
| 1533 |
reason = f'تراكم شرائي محتمل عبر {len(network_stats)} شبكة: ${abs_net_flow:,.0f} سحب من المنصات.'
|
| 1534 |
critical_alert = abs_net_flow > 750000
|
| 1535 |
-
|
| 1536 |
-
# حالة HOLD
|
| 1537 |
else:
|
| 1538 |
if whale_transfers_count > 0 and abs_net_flow < 100000:
|
| 1539 |
confidence = min(base_confidence * 1.1, 0.6)
|
| 1540 |
reason += " (صافي التدفق منخفض نسبياً)"
|
| 1541 |
-
# تعديل: إذا كان هناك تحويلات حيتان لكن التدفق متعادل، قد تكون الثقة أقل من الأساسية
|
| 1542 |
elif whale_transfers_count > 0 and abs_net_flow < 50000:
|
| 1543 |
-
confidence = max(0.4, base_confidence * 0.9)
|
| 1544 |
reason += " (صافي التدفق شبه متعادل)"
|
| 1545 |
elif whale_transfers_count == 0:
|
| 1546 |
-
confidence = 0.3
|
| 1547 |
reason = 'لا توجد تحركات حيتان كبيرة في الساعات الأخيرة'
|
| 1548 |
else:
|
| 1549 |
confidence = base_confidence
|
|
@@ -1581,33 +1550,29 @@ class EnhancedWhaleMonitor:
|
|
| 1581 |
|
| 1582 |
print(f"🔍 البحث عن عقد للعملة: {symbol}")
|
| 1583 |
|
| 1584 |
-
# 1. البحث في قاعدة البيانات المحلية
|
| 1585 |
if symbol_lower in self.contracts_db:
|
| 1586 |
contract_info = self.contracts_db[symbol_lower]
|
| 1587 |
-
if isinstance(contract_info, str):
|
| 1588 |
network = self._detect_network_from_address(contract_info)
|
| 1589 |
contract_info = {'address': contract_info, 'network': network}
|
| 1590 |
self.contracts_db[symbol_lower] = contract_info
|
| 1591 |
-
# التأكد من وجود المفاتيح المطلوبة
|
| 1592 |
if 'address' in contract_info and 'network' in contract_info:
|
| 1593 |
print(f" ✅ وجد في قاعدة البيانات المحلية: {contract_info}")
|
| 1594 |
return contract_info
|
| 1595 |
else:
|
| 1596 |
print(f" ⚠️ بيانات العقد غير مكتملة في القاعدة المحلية لـ {symbol}: {contract_info}")
|
| 1597 |
-
# سنحاول البحث عبر CoinGecko كبديل
|
| 1598 |
|
| 1599 |
-
# 2. البحث في CoinGecko
|
| 1600 |
print(f" 🔍 البحث في CoinGecko عن {base_symbol}...")
|
| 1601 |
coingecko_result = await self._find_contract_via_coingecko(base_symbol)
|
| 1602 |
|
| 1603 |
if coingecko_result:
|
| 1604 |
address, network = coingecko_result
|
| 1605 |
contract_info = {'address': address, 'network': network}
|
| 1606 |
-
self.contracts_db[symbol_lower] = contract_info
|
| 1607 |
print(f" ✅ تم العثور على عقد {symbol} عبر CoinGecko على شبكة {network}: {address}")
|
| 1608 |
|
| 1609 |
if self.r2_service:
|
| 1610 |
-
await self._save_contracts_to_r2()
|
| 1611 |
|
| 1612 |
return contract_info
|
| 1613 |
|
|
@@ -1624,6 +1589,7 @@ class EnhancedWhaleMonitor:
|
|
| 1624 |
max_retries = 2
|
| 1625 |
for attempt in range(max_retries):
|
| 1626 |
async with self.coingecko_semaphore:
|
|
|
|
| 1627 |
async with httpx.AsyncClient(timeout=15, headers=headers, verify=self.ssl_context) as client:
|
| 1628 |
try:
|
| 1629 |
print(f" 🔍 CoinGecko Search API Call (Attempt {attempt + 1}) for {symbol}")
|
|
@@ -1690,9 +1656,12 @@ class EnhancedWhaleMonitor:
|
|
| 1690 |
print(f" ❌ No contract found on supported platforms for {coin_id}")
|
| 1691 |
return None
|
| 1692 |
|
| 1693 |
-
|
| 1694 |
-
|
| 1695 |
-
|
|
|
|
|
|
|
|
|
|
| 1696 |
except Exception as inner_e:
|
| 1697 |
print(f" ❌ Unexpected error during CoinGecko API call (Attempt {attempt + 1}): {inner_e}")
|
| 1698 |
if attempt < max_retries - 1: await asyncio.sleep(1); continue
|
|
@@ -1790,9 +1759,7 @@ class EnhancedWhaleMonitor:
|
|
| 1790 |
}
|
| 1791 |
|
| 1792 |
whale_signal = whale_data.get('trading_signal', {})
|
| 1793 |
-
|
| 1794 |
-
# إضافة تفاصيل التحليل المتاحة
|
| 1795 |
-
analysis_details = whale_data # استخدام القاموس الكامل كـ تفاصيل
|
| 1796 |
|
| 1797 |
return {
|
| 1798 |
'action': whale_signal.get('action', 'HOLD'),
|
|
@@ -1801,7 +1768,7 @@ class EnhancedWhaleMonitor:
|
|
| 1801 |
'source': 'whale_analysis',
|
| 1802 |
'critical_alert': whale_signal.get('critical_alert', False),
|
| 1803 |
'data_available': True,
|
| 1804 |
-
'whale_analysis_details': analysis_details
|
| 1805 |
}
|
| 1806 |
|
| 1807 |
except Exception as e:
|
|
@@ -1823,4 +1790,4 @@ class EnhancedWhaleMonitor:
|
|
| 1823 |
print(f"⚠️ خطأ أثناء إغلاق HTTP client لمراقب الحيتان: {e}")
|
| 1824 |
|
| 1825 |
|
| 1826 |
-
print("✅ EnhancedWhaleMonitor loaded - Correct RPC (eth_getLogs), Robust Solana RPC Handling (Free Endpoints)")
|
|
|
|
| 124 |
'chain_id': 'mainnet-beta',
|
| 125 |
'native_coin': 'SOL',
|
| 126 |
'explorer': 'solscan',
|
| 127 |
+
'rpc_endpoints': self._get_solana_rpc_endpoints(),
|
| 128 |
'type': 'solana'
|
| 129 |
}
|
| 130 |
}
|
|
|
|
| 294 |
'https://rpc.ankr.com/fantom'
|
| 295 |
]
|
| 296 |
|
|
|
|
| 297 |
def _get_solana_rpc_endpoints(self):
|
| 298 |
"""الحصول على نقاط RPC مجانية لشبكة Solana (بدون مفاتيح API)"""
|
| 299 |
endpoints = [
|
| 300 |
+
'https://api.mainnet-beta.solana.com',
|
| 301 |
+
'https://ssc-dao.genesysgo.net/',
|
| 302 |
+
'https://solana-mainnet.rpc.extrnode.com',
|
| 303 |
+
'https://solana-mainnet.phantom.tech/',
|
| 304 |
+
'https://mainnet.rpc.solana.pyth.network',
|
| 305 |
+
'https://rpc.ankr.com/solana',
|
|
|
|
|
|
|
| 306 |
]
|
|
|
|
|
|
|
| 307 |
print(f"ℹ️ قائمة Solana RPC Endpoints (مجانية وبدون مفاتيح) المستخدمة: {endpoints}")
|
| 308 |
return endpoints
|
|
|
|
| 309 |
|
| 310 |
def _initialize_comprehensive_exchange_addresses(self):
|
| 311 |
"""تهيئة قاعدة بيانات شاملة لعناوين كل المنصات"""
|
|
|
|
| 330 |
'gate': [ '0x0d0707963952f2fba59dd06f2b425ace40b492fe', '0xc6f9741f5a5a676b91171804cf3c500ab438bb6e' ], # إضافة Gate.io
|
| 331 |
'uniswap': [ '0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad', '0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45' ],
|
| 332 |
'pancakeswap': [ '0x10ed43c718714eb63d5aa57b78b54704e256024e', '0x13f4ea83d0bd40e75c8222255bc855a974568dd4' ],
|
|
|
|
| 333 |
'solana_kucoin': ['F3a5ZLPKUCrCj6CGKP2m9wS9Y2L8RcUBoJq9L2D2sUYi', '2AQdpHJ2JpcEgPiATFnAKfV9hPMvouWJAhKvamQ2Krqy' ],
|
| 334 |
'solana_binance': ['5tzFkiKscXHK5ZXCGbXZJpXaWuBtZ6RrH8eL2a7Yi7Vn', '9WzDXwBbmkg8ZTbNMqUxvQRApF22xwsgMj2SUZrchK2E'],
|
| 335 |
+
'solana_coinbase': ['CeBiLnAE3UAW9mCDSjD1VULKLeQjefjN4d941fVKazSM'],
|
| 336 |
+
'solana_wormhole': ['worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth']
|
| 337 |
}
|
| 338 |
|
| 339 |
self.address_categories = {'exchange': set(), 'cex': set(), 'dex': set(), 'bridge': set(), 'whale': set(), 'unknown': set()}
|
|
|
|
| 345 |
self.address_labels[addr_lower] = category
|
| 346 |
self.address_categories['exchange'].add(addr_lower)
|
| 347 |
|
|
|
|
| 348 |
if category in ['kucoin', 'binance', 'coinbase', 'kraken', 'okx', 'gate', 'solana_kucoin', 'solana_binance', 'solana_coinbase']:
|
| 349 |
self.address_categories['cex'].add(addr_lower)
|
| 350 |
elif category in ['uniswap', 'pancakeswap']:
|
|
|
|
| 573 |
endpoint_name = endpoint.split('//')[1].split('/')[0] if '//' in endpoint else endpoint
|
| 574 |
print(f" 🔍 محاولة جلب سجلات التحويلات لـ {network} عبر {endpoint_name}...")
|
| 575 |
|
| 576 |
+
# ✅ الإصلاح: عدم استخدام "async with self.http_client"
|
| 577 |
+
# استخدام "self.http_client" مباشرة
|
| 578 |
+
payload_block = {"jsonrpc": "2.0", "method": "eth_blockNumber", "params": [], "id": int(time.time())}
|
| 579 |
+
response_block = await self.http_client.post(endpoint, json=payload_block, timeout=15.0)
|
| 580 |
+
response_block.raise_for_status()
|
| 581 |
+
json_response_block = response_block.json()
|
| 582 |
+
result_block = json_response_block.get('result')
|
| 583 |
+
if not result_block:
|
| 584 |
+
rpc_error = json_response_block.get('error')
|
| 585 |
+
error_msg = rpc_error.get('message') if rpc_error else 'No block number result'
|
| 586 |
+
print(f" ❌ لم يتم الحصول على رقم أحدث كتلة من {endpoint_name}: {error_msg}")
|
| 587 |
+
continue
|
| 588 |
+
latest_block = int(result_block, 16)
|
| 589 |
+
|
| 590 |
+
payload_latest_block_time = {"jsonrpc": "2.0", "method": "eth_getBlockByNumber", "params": [hex(latest_block), False], "id": int(time.time())+1}
|
| 591 |
+
response_latest_time = await self.http_client.post(endpoint, json=payload_latest_block_time, timeout=15.0)
|
| 592 |
+
latest_block_timestamp_approx = int(time.time())
|
| 593 |
+
if response_latest_time.status_code == 200:
|
| 594 |
+
latest_block_data = response_latest_time.json().get('result')
|
| 595 |
+
if latest_block_data and latest_block_data.get('timestamp'):
|
| 596 |
+
latest_block_timestamp_approx = int(latest_block_data['timestamp'], 16)
|
| 597 |
+
|
| 598 |
+
from_block = max(0, latest_block - blocks_to_scan)
|
| 599 |
+
print(f" 📦 فحص الكتل من {from_block} إلى {latest_block} (آخر {blocks_to_scan} كتل تقريباً)")
|
| 600 |
+
|
| 601 |
+
payload_logs = {
|
| 602 |
+
"jsonrpc": "2.0", "method": "eth_getLogs",
|
| 603 |
+
"params": [{"fromBlock": hex(from_block), "toBlock": hex(latest_block), "address": contract_address_checksum, "topics": [TRANSFER_EVENT_SIGNATURE]}],
|
| 604 |
+
"id": int(time.time())+2
|
| 605 |
+
}
|
| 606 |
+
response_logs = await self.http_client.post(endpoint, json=payload_logs, timeout=45.0)
|
| 607 |
+
response_logs.raise_for_status()
|
| 608 |
+
json_response_logs = response_logs.json()
|
| 609 |
+
logs = json_response_logs.get('result')
|
| 610 |
+
|
| 611 |
+
if logs is None:
|
| 612 |
+
rpc_error = json_response_logs.get('error')
|
| 613 |
+
error_msg = rpc_error.get('message') if rpc_error else 'Invalid logs response'
|
| 614 |
+
print(f" ❌ استجابة السجلات غير صالحة من {endpoint_name}: {error_msg}")
|
| 615 |
+
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 616 |
|
| 617 |
+
if not logs:
|
| 618 |
+
print(f" ✅ لا توجد سجلات تحويل لـ {contract_address} في آخر {blocks_to_scan} كتل عبر {endpoint_name}")
|
| 619 |
successful_endpoint = endpoint_name
|
| 620 |
break
|
| 621 |
|
| 622 |
+
print(f" 📊 تم العثور على {len(logs)} سجل تحويل محتمل.")
|
| 623 |
+
|
| 624 |
+
for log in logs:
|
| 625 |
+
try:
|
| 626 |
+
topics = log.get('topics', [])
|
| 627 |
+
data = log.get('data', '0x')
|
| 628 |
+
block_num_hex = log.get('blockNumber')
|
| 629 |
+
tx_hash = log.get('transactionHash')
|
| 630 |
+
log_index_hex = log.get('logIndex', '0x0')
|
| 631 |
+
|
| 632 |
+
if len(topics) == 3 and data != '0x' and block_num_hex and tx_hash:
|
| 633 |
+
sender = '0x' + topics[1][26:]
|
| 634 |
+
receiver = '0x' + topics[2][26:]
|
| 635 |
+
value = str(int(data, 16))
|
| 636 |
+
block_num = str(int(block_num_hex, 16))
|
| 637 |
+
log_index = str(int(log_index_hex, 16))
|
| 638 |
+
|
| 639 |
+
transfers.append({
|
| 640 |
+
'hash': tx_hash, 'from': sender.lower(), 'to': receiver.lower(),
|
| 641 |
+
'value': value, 'timeStamp': str(latest_block_timestamp_approx),
|
| 642 |
+
'blockNumber': block_num, 'network': network, 'logIndex': log_index
|
| 643 |
+
})
|
| 644 |
+
else:
|
| 645 |
+
print(f" ⚠️ سجل غير متوافق مع Transfer Event: Topics={len(topics)}, Data={data[:10]}...")
|
| 646 |
+
|
| 647 |
+
except (ValueError, TypeError, KeyError, IndexError) as log_parse_error:
|
| 648 |
+
print(f" ⚠️ خطأ في تحليل سجل فردي: {log_parse_error} - Log: {log}")
|
| 649 |
+
continue
|
| 650 |
+
|
| 651 |
+
print(f" ✅ تم تحليل {len(transfers)} تحويلة بنجاح من {endpoint_name}")
|
| 652 |
+
successful_endpoint = endpoint_name
|
| 653 |
+
break
|
| 654 |
+
|
| 655 |
except ssl.SSLCertVerificationError as ssl_err:
|
| 656 |
print(f" ❌ خطأ SSL مع {endpoint_name}: {ssl_err}")
|
| 657 |
continue
|
|
|
|
| 665 |
print(f" ❌ خطأ في تحليل استجابة JSON من {endpoint_name}: {json_err}")
|
| 666 |
continue
|
| 667 |
except Exception as e:
|
| 668 |
+
# ✅ التعامل مع الخطأ الذي أبلغت عنه
|
| 669 |
+
if "Cannot reopen a client instance" in str(e):
|
| 670 |
+
print(f" ❌ فشل فادح: محاولة استخدام client مغلق. {e}")
|
| 671 |
+
# هذا لا يجب أن يحدث الآن، ولكن كإجراء وقائي، نكسر الحلقة
|
| 672 |
+
return transfers # إرجاع ما تم تجميعه حتى الآن
|
| 673 |
print(f" ❌ فشل غير متوقع مع {endpoint_name}: {e}")
|
| 674 |
traceback.print_exc()
|
| 675 |
continue
|
|
|
|
| 709 |
"params": [ token_address, {"limit": limit, "commitment": "confirmed"} ]
|
| 710 |
}
|
| 711 |
|
| 712 |
+
# ✅ الإصلاح: عدم استخدام "async with self.http_client"
|
| 713 |
+
response_signatures = await self.http_client.post(endpoint, json=payload_signatures, timeout=20.0)
|
| 714 |
|
| 715 |
if response_signatures.status_code == 403:
|
| 716 |
print(f" ⚠️ Solana endpoint {endpoint_name} failed: 403 Forbidden")
|
|
|
|
| 744 |
|
| 745 |
processed_count = 0
|
| 746 |
transaction_tasks = []
|
| 747 |
+
# ✅ الإصلاح: تمرير "self.http_client" مباشرة
|
| 748 |
+
for signature in signatures[:20]:
|
| 749 |
+
transaction_tasks.append(
|
| 750 |
+
self._get_solana_transaction_detail_with_retry(signature, endpoint, self.http_client)
|
| 751 |
+
)
|
| 752 |
+
transaction_details = await asyncio.gather(*transaction_tasks)
|
| 753 |
|
| 754 |
for detail_result in transaction_details:
|
| 755 |
if detail_result and self._is_solana_token_transfer(detail_result, token_address):
|
|
|
|
| 775 |
print(f" ❌ خطأ في تحليل استجابة JSON من Solana ({endpoint_name}): {json_err}")
|
| 776 |
continue
|
| 777 |
except Exception as e:
|
| 778 |
+
# ✅ التعامل مع الخطأ الذي أبلغت عنه
|
| 779 |
+
if "Cannot reopen a client instance" in str(e):
|
| 780 |
+
print(f" ❌ فشل فادح: محاولة استخدام client مغلق. {e}")
|
| 781 |
+
return transfers
|
| 782 |
print(f" ❌ فشل غير متوقع مع endpoint Solana ({endpoint_name}): {e}")
|
| 783 |
traceback.print_exc()
|
| 784 |
continue
|
|
|
|
| 862 |
print(f" ❌ خطأ SSL في getTransaction لـ {signature[:10]}... : {ssl_err}")
|
| 863 |
raise ssl_err
|
| 864 |
except json.JSONDecodeError as json_err:
|
| 865 |
+
print(f" ❌ خطأ JSON في getTransaction لـ {signature[:10]}... : {response.text[:200]}")
|
| 866 |
return None
|
| 867 |
except Exception as e:
|
| 868 |
print(f" ❌ استثناء غير متوقع في getTransaction لـ {signature[:10]}... : {e}")
|
|
|
|
| 892 |
all_balances = (post_balances or []) + (pre_balances or [])
|
| 893 |
for balance in all_balances:
|
| 894 |
if isinstance(balance, dict) and balance.get('mint') == token_address:
|
|
|
|
| 895 |
pre_amount = next((pb.get('uiTokenAmount',{}).get('amount','0') for pb in (pre_balances or []) if isinstance(pb, dict) and pb.get('owner') == balance.get('owner') and pb.get('mint') == token_address), '0')
|
| 896 |
post_amount = balance.get('uiTokenAmount',{}).get('amount','0')
|
| 897 |
try:
|
| 898 |
if int(post_amount) != int(pre_amount):
|
| 899 |
return True
|
| 900 |
except (ValueError, TypeError):
|
| 901 |
+
pass
|
| 902 |
|
| 903 |
return False
|
| 904 |
except Exception as e:
|
|
|
|
| 905 |
return False
|
| 906 |
|
| 907 |
async def _parse_solana_transfer(self, transaction, token_address):
|
|
|
|
| 943 |
amount_raw = diff_raw
|
| 944 |
elif diff_raw < 0:
|
| 945 |
sender = owner
|
|
|
|
| 946 |
|
| 947 |
except (ValueError, TypeError):
|
| 948 |
print(f" ⚠️ تحذير: فشل تحليل القيمة الخام لـ Solana ({owner}).")
|
|
|
|
| 949 |
pre_ui = pre_bal_data.get('uiAmount', 0.0) or 0.0
|
| 950 |
post_ui = post_bal_data.get('uiAmount', 0.0) or 0.0
|
| 951 |
diff_ui = post_ui - pre_ui
|
|
|
|
| 957 |
elif diff_ui < -1e-9:
|
| 958 |
sender = owner
|
| 959 |
|
|
|
|
| 960 |
if not sender or not receiver or amount_raw == 0:
|
| 961 |
inner_instructions = meta.get('innerInstructions', [])
|
| 962 |
for inner_inst_set in inner_instructions:
|
|
|
|
| 964 |
parsed = inst.get('parsed')
|
| 965 |
if isinstance(parsed, dict) and parsed.get('type') in ['transfer', 'transferChecked'] and parsed.get('info', {}).get('mint') == token_address:
|
| 966 |
info = parsed.get('info', {})
|
| 967 |
+
sender_inner = info.get('source') or info.get('authority')
|
| 968 |
receiver_inner = info.get('destination')
|
| 969 |
+
amount_inner_str = info.get('tokenAmount', {}).get('amount') or info.get('amount')
|
| 970 |
try:
|
| 971 |
amount_inner_raw = int(amount_inner_str)
|
| 972 |
if sender_inner and receiver_inner and amount_inner_raw > 0:
|
| 973 |
sender = sender_inner
|
| 974 |
receiver = receiver_inner
|
| 975 |
amount_raw = amount_inner_raw
|
|
|
|
| 976 |
break
|
| 977 |
except (ValueError, TypeError): continue
|
| 978 |
+
if sender and receiver and amount_raw > 0: break
|
| 979 |
+
if sender and receiver and amount_raw > 0: break
|
|
|
|
| 980 |
|
|
|
|
| 981 |
if sender and receiver and amount_raw > 0:
|
| 982 |
return {
|
| 983 |
'hash': signature, 'from': sender, 'to': receiver,
|
|
|
|
| 986 |
'type': 'solana_transfer', 'logIndex': '0'
|
| 987 |
}
|
| 988 |
else:
|
|
|
|
| 989 |
return None
|
| 990 |
|
| 991 |
except Exception as e:
|
|
|
|
| 1005 |
'ethereum': {'url': 'https://api.etherscan.io/api', 'key': self.etherscan_key},
|
| 1006 |
'bsc': {'url': 'https://api.bscscan.com/api', 'key': self.bscscan_key},
|
| 1007 |
'polygon': {'url': 'https://api.polygonscan.com/api', 'key': self.polygonscan_key}
|
|
|
|
| 1008 |
}
|
| 1009 |
|
| 1010 |
if network not in explorer_config or not explorer_config[network].get('key'):
|
|
|
|
| 1011 |
return []
|
| 1012 |
|
| 1013 |
config = explorer_config[network]
|
| 1014 |
+
address_param = "contractaddress" if network == 'ethereum' else "address"
|
|
|
|
| 1015 |
|
| 1016 |
params = {
|
| 1017 |
+
"module": "account", "action": "tokentx",
|
| 1018 |
+
address_param: contract_address,
|
| 1019 |
+
"page": 1, "offset": 100, "startblock": 0,
|
| 1020 |
+
"endblock": 999999999, "sort": "desc", "apikey": config['key']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1021 |
}
|
| 1022 |
|
| 1023 |
+
async with httpx.AsyncClient(timeout=20.0, verify=self.ssl_context) as client: # استخدام سياق SSL
|
| 1024 |
response = await client.get(config['url'], params=params)
|
| 1025 |
|
| 1026 |
if response.status_code == 200:
|
|
|
|
| 1032 |
transfers = data.get('result', [])
|
| 1033 |
processed_transfers = []
|
| 1034 |
for tf in transfers:
|
|
|
|
|
|
|
| 1035 |
if tf.get('tokenSymbol') and tf.get('contractAddress','').lower() == contract_address.lower():
|
| 1036 |
tf['network'] = network
|
| 1037 |
if 'from' in tf: tf['from'] = tf['from'].lower()
|
| 1038 |
if 'to' in tf: tf['to'] = tf['to'].lower()
|
| 1039 |
+
if 'logIndex' not in tf: tf['logIndex'] = 'N/A'
|
| 1040 |
processed_transfers.append(tf)
|
| 1041 |
|
| 1042 |
print(f" ✅ Explorer {network}: تم جلب {len(processed_transfers)} تحويلة توكن للعقد المحدد.")
|
|
|
|
| 1214 |
'llm_friendly_summary': llm_summary
|
| 1215 |
}
|
| 1216 |
|
| 1217 |
+
|
| 1218 |
async def _get_accurate_token_value_optimized(self, raw_value, decimals, symbol):
|
| 1219 |
"""حساب القيمة بالدولار باستخدام decimals مُمررة والسعر الحالي"""
|
| 1220 |
try:
|
|
|
|
| 1260 |
"params": [{"to": contract_address, "data": "0x313ce567"}, "latest"],
|
| 1261 |
"id": int(time.time())
|
| 1262 |
}
|
| 1263 |
+
# ✅ الإصلاح: استخدام "self.http_client" مباشرة
|
| 1264 |
+
response = await self.http_client.post(endpoint, json=payload, timeout=10.0)
|
| 1265 |
if response.status_code == 200:
|
| 1266 |
result = response.json().get('result')
|
| 1267 |
if result and result != '0x' and result.startswith('0x'):
|
|
|
|
| 1275 |
print(f" ⚠️ فشل تحويل نتيجة decimals من RPC ({result}) لـ {contract_address} على {network}")
|
| 1276 |
continue
|
| 1277 |
except Exception as rpc_decimal_error:
|
| 1278 |
+
# ✅ التعامل مع خطأ العميل المغلق
|
| 1279 |
+
if "Cannot reopen a client instance" in str(rpc_decimal_error):
|
| 1280 |
+
print(f" ❌ فشل فادح (Decimals): محاولة استخدام client مغلق. {rpc_decimal_error}")
|
| 1281 |
+
return None # لا يمكن المتابعة
|
| 1282 |
print(f" ⚠️ خطأ RPC أثناء جلب decimals من {endpoint.split('//')[-1]}: {rpc_decimal_error}")
|
| 1283 |
continue
|
| 1284 |
|
| 1285 |
elif network == 'solana':
|
| 1286 |
print(f"ℹ️ جلب decimals لـ Solana ({contract_address}) غير مدعوم حاليًا عبر RPC.")
|
| 1287 |
+
estimated_decimals = 9
|
|
|
|
| 1288 |
self.token_decimals_cache[cache_key] = estimated_decimals
|
| 1289 |
print(f" ⚠️ استخدام قيمة decimals تقديرية لـ Solana: {estimated_decimals}")
|
| 1290 |
return estimated_decimals
|
| 1291 |
|
|
|
|
| 1292 |
print(f"❌ فشل جلب الكسور العشرية للعقد {contract_address} على شبكة {network} من جميع المصادر.")
|
| 1293 |
return None
|
| 1294 |
|
|
|
|
| 1305 |
if cache_entry and time.time() - cache_entry.get('timestamp', 0) < 300:
|
| 1306 |
return cache_entry['price']
|
| 1307 |
|
|
|
|
| 1308 |
price = await self._get_token_price_from_coingecko(symbol)
|
| 1309 |
if price is not None and price > 0:
|
| 1310 |
self.token_price_cache[base_symbol] = {'price': price, 'timestamp': time.time()}
|
| 1311 |
return price
|
| 1312 |
|
|
|
|
| 1313 |
price = await self._get_token_price_from_kucoin(symbol)
|
| 1314 |
if price is not None and price > 0:
|
| 1315 |
self.token_price_cache[base_symbol] = {'price': price, 'timestamp': time.time()}
|
|
|
|
| 1333 |
'LTC': 'litecoin', 'XLM': 'stellar', 'TRX': 'tron', 'EOS': 'eos', 'XMR': 'monero',
|
| 1334 |
'SOL': 'solana', 'MATIC': 'matic-network', 'AVAX': 'avalanche-2', 'LINK': 'chainlink',
|
| 1335 |
'ATOM': 'cosmos', 'UNI': 'uniswap', 'AAVE': 'aave', 'KCS': 'kucoin-shares',
|
|
|
|
| 1336 |
'MAVIA': 'heroes-of-mavia', 'COMMON': 'commonwealth', 'WLFI': 'wolfi',
|
| 1337 |
'PINGPONG': 'pingpong', 'YB': 'yourbusd', 'REACT': 'react', 'XMN': 'xmine',
|
| 1338 |
'ANOME': 'anome', 'ZEN': 'zencash', 'AKT': 'akash-network', 'UB': 'unibit'
|
|
|
|
| 1341 |
base_symbol = symbol.split('/')[0].upper()
|
| 1342 |
coingecko_id = symbol_mapping.get(base_symbol)
|
| 1343 |
if not coingecko_id:
|
| 1344 |
+
coingecko_id = base_symbol.lower()
|
| 1345 |
print(f"ℹ️ رمز {base_symbol} غير موجود في symbol_mapping، استخدام {coingecko_id} لـ CoinGecko.")
|
| 1346 |
|
| 1347 |
|
|
|
|
| 1351 |
max_retries = 2
|
| 1352 |
for attempt in range(max_retries):
|
| 1353 |
async with self.coingecko_semaphore:
|
| 1354 |
+
# ✅ الإصلاح: إنشاء client جديد ومؤقت هنا، وليس استخدام self.http_client
|
| 1355 |
async with httpx.AsyncClient(timeout=10.0, headers=headers, verify=self.ssl_context) as client:
|
| 1356 |
try:
|
| 1357 |
response = await client.get(url)
|
|
|
|
| 1372 |
print(f"⚠️ استجابة CoinGecko تحتوي على سعر غير صالح لـ {symbol}: {price}")
|
| 1373 |
return 0
|
| 1374 |
else:
|
|
|
|
| 1375 |
if attempt == 0:
|
| 1376 |
print(f"⚠️ لم يتم العثور على سعر لـ {coingecko_id} في CoinGecko، محاولة البحث عن المعرف...")
|
| 1377 |
+
await asyncio.sleep(1.1)
|
| 1378 |
found_id = await self._find_coingecko_id_via_search(base_symbol, client)
|
| 1379 |
if found_id and found_id != coingecko_id:
|
| 1380 |
print(f" 🔄 تم العثور على معرف بديل: {found_id}. إعادة المحاولة...")
|
| 1381 |
+
coingecko_id = found_id
|
|
|
|
| 1382 |
url = f"https://api.coingecko.com/api/v3/simple/price?ids={coingecko_id}&vs_currencies=usd"
|
| 1383 |
+
continue
|
| 1384 |
else:
|
| 1385 |
print(f" ❌ لم يتم العثور على معرف بديل لـ {base_symbol}.")
|
| 1386 |
+
return 0
|
| 1387 |
+
else:
|
| 1388 |
print(f"⚠️ لم يتم العثور على سعر لـ {coingecko_id} في استجابة CoinGecko بعد البحث.")
|
| 1389 |
return 0
|
| 1390 |
|
|
|
|
| 1391 |
except httpx.HTTPStatusError as http_err:
|
| 1392 |
print(f"❌ خطأ HTTP من CoinGecko لـ {symbol}: {http_err.response.status_code}")
|
| 1393 |
return 0
|
|
|
|
| 1405 |
print(f"❌ فشل عام في _get_token_price_from_coingecko لـ {symbol}: {e}")
|
| 1406 |
return 0
|
| 1407 |
|
|
|
|
| 1408 |
async def _find_coingecko_id_via_search(self, symbol, client: httpx.AsyncClient):
|
| 1409 |
+
"""دالة مساعدة للبحث عن معرف CoinGecko"""
|
| 1410 |
try:
|
| 1411 |
search_url = f"https://api.coingecko.com/api/v3/search?query={symbol}"
|
| 1412 |
response = await client.get(search_url)
|
|
|
|
| 1414 |
data = response.json()
|
| 1415 |
coins = data.get('coins', [])
|
| 1416 |
if coins:
|
|
|
|
| 1417 |
search_symbol_lower = symbol.lower()
|
| 1418 |
for coin in coins:
|
| 1419 |
if coin.get('symbol', '').lower() == search_symbol_lower:
|
| 1420 |
return coin.get('id')
|
|
|
|
| 1421 |
return coins[0].get('id')
|
| 1422 |
return None
|
| 1423 |
except Exception as e:
|
|
|
|
| 1429 |
"""جلب سعر العملة من KuCoin كبديل (بشكل غير متزامن)"""
|
| 1430 |
exchange = None
|
| 1431 |
try:
|
|
|
|
| 1432 |
exchange = ccxt.kucoin({'enableRateLimit': True})
|
|
|
|
| 1433 |
markets = await exchange.load_markets()
|
| 1434 |
if symbol not in markets:
|
| 1435 |
print(f"⚠️ الرمز {symbol} غير موجود في أسواق KuCoin.")
|
| 1436 |
+
await exchange.close() # الإغلاق عند عدم العثور على الرمز
|
| 1437 |
return 0
|
| 1438 |
|
| 1439 |
ticker = await exchange.fetch_ticker(symbol)
|
|
|
|
| 1458 |
print(f"❌ خطأ شبكة أثناء جلب السعر من KuCoin لـ {symbol}: {e}")
|
| 1459 |
return 0
|
| 1460 |
except ccxt.ExchangeError as e:
|
|
|
|
| 1461 |
print(f"❌ خطأ منصة KuCoin ({type(e).__name__}) لـ {symbol}: {e}")
|
| 1462 |
return 0
|
| 1463 |
except Exception as e:
|
|
|
|
| 1477 |
abs_net_flow = abs(net_flow_usd)
|
| 1478 |
total_flow_volume = total_to_exchanges_usd + total_from_exchanges_usd
|
| 1479 |
|
|
|
|
| 1480 |
confidence_multiplier = min( (abs_net_flow / 500000.0) + (whale_transfers_count / 5.0) , 1.5)
|
|
|
|
| 1481 |
base_confidence = 0.5 + (network_strength * 0.1)
|
| 1482 |
|
| 1483 |
action = 'HOLD'
|
| 1484 |
reason = f'نشاط حيتان عبر {len(network_stats)} شبكة: {whale_transfers_count} تحويلة كبيرة بقيمة إجمالية ${total_flow_volume:,.0f}. صافي التدفق ${net_flow_usd:,.0f}'
|
| 1485 |
critical_alert = False
|
| 1486 |
|
| 1487 |
+
if net_flow_usd > 500000 and deposit_count >= 2:
|
|
|
|
| 1488 |
action = 'STRONG_SELL'
|
| 1489 |
confidence = min(0.7 + (base_confidence * confidence_multiplier * 0.3), 0.95)
|
| 1490 |
reason = f'ضغط بيعي قوي عبر {len(network_stats)} شبكة: ${net_flow_usd:,.0f} إيداع للمنصات ({deposit_count} تحويلة).'
|
| 1491 |
critical_alert = abs_net_flow > 1000000
|
| 1492 |
+
elif net_flow_usd > 150000 and (deposit_count >= 1 or whale_transfers_count > 0):
|
| 1493 |
action = 'SELL'
|
| 1494 |
confidence = min(0.6 + (base_confidence * confidence_multiplier * 0.2), 0.85)
|
| 1495 |
reason = f'ضغط بيعي محتمل عبر {len(network_stats)} شبكة: ${net_flow_usd:,.0f} إيداع للمنصات.'
|
| 1496 |
critical_alert = abs_net_flow > 750000
|
| 1497 |
+
elif net_flow_usd < -500000 and withdrawal_count >= 2:
|
|
|
|
|
|
|
| 1498 |
action = 'STRONG_BUY'
|
| 1499 |
confidence = min(0.7 + (base_confidence * confidence_multiplier * 0.3), 0.95)
|
| 1500 |
reason = f'تراكم شرائي قوي عبر {len(network_stats)} شبكة: ${abs_net_flow:,.0f} سحب من المنصات ({withdrawal_count} تحويلة).'
|
| 1501 |
critical_alert = abs_net_flow > 1000000
|
| 1502 |
+
elif net_flow_usd < -150000 and (withdrawal_count >= 1 or whale_transfers_count > 0):
|
| 1503 |
action = 'BUY'
|
| 1504 |
confidence = min(0.6 + (base_confidence * confidence_multiplier * 0.2), 0.85)
|
| 1505 |
reason = f'تراكم شرائي محتمل عبر {len(network_stats)} شبكة: ${abs_net_flow:,.0f} سحب من المنصات.'
|
| 1506 |
critical_alert = abs_net_flow > 750000
|
|
|
|
|
|
|
| 1507 |
else:
|
| 1508 |
if whale_transfers_count > 0 and abs_net_flow < 100000:
|
| 1509 |
confidence = min(base_confidence * 1.1, 0.6)
|
| 1510 |
reason += " (صافي التدفق منخفض نسبياً)"
|
|
|
|
| 1511 |
elif whale_transfers_count > 0 and abs_net_flow < 50000:
|
| 1512 |
+
confidence = max(0.4, base_confidence * 0.9)
|
| 1513 |
reason += " (صافي التدفق شبه متعادل)"
|
| 1514 |
elif whale_transfers_count == 0:
|
| 1515 |
+
confidence = 0.3
|
| 1516 |
reason = 'لا توجد تحركات حيتان كبيرة في الساعات الأخيرة'
|
| 1517 |
else:
|
| 1518 |
confidence = base_confidence
|
|
|
|
| 1550 |
|
| 1551 |
print(f"🔍 البحث عن عقد للعملة: {symbol}")
|
| 1552 |
|
|
|
|
| 1553 |
if symbol_lower in self.contracts_db:
|
| 1554 |
contract_info = self.contracts_db[symbol_lower]
|
| 1555 |
+
if isinstance(contract_info, str):
|
| 1556 |
network = self._detect_network_from_address(contract_info)
|
| 1557 |
contract_info = {'address': contract_info, 'network': network}
|
| 1558 |
self.contracts_db[symbol_lower] = contract_info
|
|
|
|
| 1559 |
if 'address' in contract_info and 'network' in contract_info:
|
| 1560 |
print(f" ✅ وجد في قاعدة البيانات المحلية: {contract_info}")
|
| 1561 |
return contract_info
|
| 1562 |
else:
|
| 1563 |
print(f" ⚠️ بيانات العقد غير مكتملة في القاعدة المحلية لـ {symbol}: {contract_info}")
|
|
|
|
| 1564 |
|
|
|
|
| 1565 |
print(f" 🔍 البحث في CoinGecko عن {base_symbol}...")
|
| 1566 |
coingecko_result = await self._find_contract_via_coingecko(base_symbol)
|
| 1567 |
|
| 1568 |
if coingecko_result:
|
| 1569 |
address, network = coingecko_result
|
| 1570 |
contract_info = {'address': address, 'network': network}
|
| 1571 |
+
self.contracts_db[symbol_lower] = contract_info
|
| 1572 |
print(f" ✅ تم العثور على عقد {symbol} عبر CoinGecko على شبكة {network}: {address}")
|
| 1573 |
|
| 1574 |
if self.r2_service:
|
| 1575 |
+
await self._save_contracts_to_r2()
|
| 1576 |
|
| 1577 |
return contract_info
|
| 1578 |
|
|
|
|
| 1589 |
max_retries = 2
|
| 1590 |
for attempt in range(max_retries):
|
| 1591 |
async with self.coingecko_semaphore:
|
| 1592 |
+
# ✅ استخدام client جديد ومؤقت هنا
|
| 1593 |
async with httpx.AsyncClient(timeout=15, headers=headers, verify=self.ssl_context) as client:
|
| 1594 |
try:
|
| 1595 |
print(f" 🔍 CoinGecko Search API Call (Attempt {attempt + 1}) for {symbol}")
|
|
|
|
| 1656 |
print(f" ❌ No contract found on supported platforms for {coin_id}")
|
| 1657 |
return None
|
| 1658 |
|
| 1659 |
+
except httpx.HTTPStatusError as http_err:
|
| 1660 |
+
print(f" ❌ HTTP Error from CoinGecko ({http_err.request.url}): {http_err.response.status_code}")
|
| 1661 |
+
return None
|
| 1662 |
+
except httpx.RequestError as req_err:
|
| 1663 |
+
print(f" ❌ Connection Error with CoinGecko ({req_err.request.url}): {req_err}")
|
| 1664 |
+
return None
|
| 1665 |
except Exception as inner_e:
|
| 1666 |
print(f" ❌ Unexpected error during CoinGecko API call (Attempt {attempt + 1}): {inner_e}")
|
| 1667 |
if attempt < max_retries - 1: await asyncio.sleep(1); continue
|
|
|
|
| 1759 |
}
|
| 1760 |
|
| 1761 |
whale_signal = whale_data.get('trading_signal', {})
|
| 1762 |
+
analysis_details = whale_data # إرجاع كافة التفاصيل
|
|
|
|
|
|
|
| 1763 |
|
| 1764 |
return {
|
| 1765 |
'action': whale_signal.get('action', 'HOLD'),
|
|
|
|
| 1768 |
'source': 'whale_analysis',
|
| 1769 |
'critical_alert': whale_signal.get('critical_alert', False),
|
| 1770 |
'data_available': True,
|
| 1771 |
+
'whale_analysis_details': analysis_details
|
| 1772 |
}
|
| 1773 |
|
| 1774 |
except Exception as e:
|
|
|
|
| 1790 |
print(f"⚠️ خطأ أثناء إغلاق HTTP client لمراقب الحيتان: {e}")
|
| 1791 |
|
| 1792 |
|
| 1793 |
+
print("✅ EnhancedWhaleMonitor loaded - Correct RPC (eth_getLogs), Robust Solana RPC Handling (Free Endpoints), Fixed httpx client usage")
|