Spaces:
Running
Running
Update whale_news_data.py
Browse files- whale_news_data.py +26 -184
whale_news_data.py
CHANGED
|
@@ -8,10 +8,20 @@ from datetime import datetime, timedelta
|
|
| 8 |
from collections import defaultdict, deque
|
| 9 |
import ccxt.pro as ccxt
|
| 10 |
import numpy as np
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
class EnhancedWhaleMonitor:
|
| 13 |
def __init__(self, contracts_db=None, r2_service=None):
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
self.moralis_key = os.getenv("MORALIS_KEY")
|
| 17 |
self.etherscan_key = os.getenv("ETHERSCAN_KEY")
|
|
@@ -99,8 +109,8 @@ class EnhancedWhaleMonitor:
|
|
| 99 |
self.price_cache = {}
|
| 100 |
self.last_scan_time = {}
|
| 101 |
|
| 102 |
-
self.contract_cache = {}
|
| 103 |
-
self.symbol_networks = {}
|
| 104 |
|
| 105 |
self.pattern_performance = {}
|
| 106 |
self.pattern_success_rates = {}
|
|
@@ -108,7 +118,6 @@ class EnhancedWhaleMonitor:
|
|
| 108 |
if self.r2_service:
|
| 109 |
asyncio.create_task(self._load_pattern_performance())
|
| 110 |
|
| 111 |
-
# تحميل العقود من R2 إذا كان متوفراً
|
| 112 |
if self.r2_service:
|
| 113 |
asyncio.create_task(self._load_contracts_from_r2())
|
| 114 |
|
|
@@ -159,7 +168,6 @@ class EnhancedWhaleMonitor:
|
|
| 159 |
response = self.r2_service.s3_client.get_object(Bucket="trading", Key=key)
|
| 160 |
contracts_data = json.loads(response['Body'].read())
|
| 161 |
|
| 162 |
-
# تحديث قاعدة البيانات المحلية
|
| 163 |
self.contracts_db.update(contracts_data)
|
| 164 |
print(f"✅ تم تحميل {len(contracts_data)} عقد من R2")
|
| 165 |
|
|
@@ -195,7 +203,6 @@ class EnhancedWhaleMonitor:
|
|
| 195 |
|
| 196 |
async def _load_pattern_performance(self):
|
| 197 |
if not self.r2_service:
|
| 198 |
-
print("⚠️ R2 Service غير متوفر لتحميل بيانات الأنماط")
|
| 199 |
return
|
| 200 |
|
| 201 |
try:
|
|
@@ -204,15 +211,12 @@ class EnhancedWhaleMonitor:
|
|
| 204 |
data = json.loads(response['Body'].read())
|
| 205 |
self.pattern_performance = data.get('pattern_performance', {})
|
| 206 |
self.pattern_success_rates = data.get('pattern_success_rates', {})
|
| 207 |
-
print(f"✅ تم تحميل بيانات أداء الأنماط: {len(self.pattern_performance)} نمط")
|
| 208 |
except Exception as e:
|
| 209 |
-
print(f"⚠️ لم يتم العثور على بيانات أداء الأنماط: {e}")
|
| 210 |
self.pattern_performance = {}
|
| 211 |
self.pattern_success_rates = {}
|
| 212 |
|
| 213 |
async def _save_pattern_performance(self):
|
| 214 |
if not self.r2_service:
|
| 215 |
-
print("⚠️ R2 Service غير متوفر لحفظ بيانات الأنماط")
|
| 216 |
return
|
| 217 |
|
| 218 |
try:
|
|
@@ -228,7 +232,6 @@ class EnhancedWhaleMonitor:
|
|
| 228 |
self.r2_service.s3_client.put_object(
|
| 229 |
Bucket="trading", Key=key, Body=data_json, ContentType="application/json"
|
| 230 |
)
|
| 231 |
-
print(f"✅ تم حفظ بيانات أداء الأنماط: {len(self.pattern_performance)} نمط")
|
| 232 |
except Exception as e:
|
| 233 |
print(f"❌ فشل حفظ بيانات أداء الأنماط: {e}")
|
| 234 |
|
|
@@ -262,7 +265,6 @@ class EnhancedWhaleMonitor:
|
|
| 262 |
self.address_categories['unknown'].add(address_lower)
|
| 263 |
return 'unknown'
|
| 264 |
except Exception as e:
|
| 265 |
-
print(f"❌ خطأ في تصنيف العنوان: {e}")
|
| 266 |
return 'unknown'
|
| 267 |
|
| 268 |
def _detect_exchange_pattern(self, transactions):
|
|
@@ -300,7 +302,6 @@ class EnhancedWhaleMonitor:
|
|
| 300 |
address_lower in self.address_categories['exchange'] or
|
| 301 |
self.address_labels.get(address_lower) in ['cex', 'suspected_cex'])
|
| 302 |
except Exception as e:
|
| 303 |
-
print(f"❌ خطأ في التحقق من عنوان التبادل: {e}")
|
| 304 |
return False
|
| 305 |
|
| 306 |
async def _update_netflow_metrics(self, network, token_symbol, from_address, to_address, value_usd, transaction_hash):
|
|
@@ -330,7 +331,7 @@ class EnhancedWhaleMonitor:
|
|
| 330 |
self.netflow_data[network][token_symbol]['netflow'].append(current_netflow)
|
| 331 |
|
| 332 |
except Exception as e:
|
| 333 |
-
|
| 334 |
|
| 335 |
def _initialize_token_metrics(self, network, token_symbol):
|
| 336 |
self.netflow_data[network][token_symbol] = {
|
|
@@ -370,7 +371,6 @@ class EnhancedWhaleMonitor:
|
|
| 370 |
return zscore
|
| 371 |
|
| 372 |
except Exception as e:
|
| 373 |
-
print(f"❌ خطأ في حساب Z-score: {e}")
|
| 374 |
return 0
|
| 375 |
|
| 376 |
def _generate_netflow_signal(self, network, token_symbol):
|
|
@@ -445,7 +445,6 @@ class EnhancedWhaleMonitor:
|
|
| 445 |
return signal
|
| 446 |
|
| 447 |
except Exception as e:
|
| 448 |
-
print(f"❌ خطأ في توليد إشارة التداول: {e}")
|
| 449 |
return None
|
| 450 |
|
| 451 |
async def _scan_single_evm_network(self, network):
|
|
@@ -454,17 +453,14 @@ class EnhancedWhaleMonitor:
|
|
| 454 |
|
| 455 |
try:
|
| 456 |
if not self.data_manager:
|
| 457 |
-
print(f"⚠️ DataManager غير متوفر لمسح شبكة {network}")
|
| 458 |
return [], []
|
| 459 |
|
| 460 |
price_usd = await self.data_manager.get_native_coin_price(network)
|
| 461 |
if price_usd is None:
|
| 462 |
-
print(f"⚠️ لم يتم الحصول على سعر لـ {network}")
|
| 463 |
return [], []
|
| 464 |
|
| 465 |
latest_block_hex = await self._call_rpc_async(network, 'eth_blockNumber')
|
| 466 |
if not latest_block_hex:
|
| 467 |
-
print(f"⚠️ فشل الحصول على أحدث كتلة لـ {network}")
|
| 468 |
return [], []
|
| 469 |
|
| 470 |
latest_block = int(latest_block_hex, 16)
|
|
@@ -522,23 +518,19 @@ class EnhancedWhaleMonitor:
|
|
| 522 |
})
|
| 523 |
whale_transactions_found += 1
|
| 524 |
|
| 525 |
-
|
| 526 |
-
await asyncio.sleep(0.1)
|
| 527 |
|
| 528 |
signal = self._generate_netflow_signal(network, 'NATIVE')
|
| 529 |
if signal:
|
| 530 |
trading_signals.append(signal)
|
| 531 |
|
| 532 |
-
print(f"✅ تم مسح {scanned_blocks} كتلة في {network} - تم العثور على {whale_transactions_found} معاملة حوت")
|
| 533 |
return whale_alerts, trading_signals
|
| 534 |
|
| 535 |
except Exception as e:
|
| 536 |
-
print(f"❌ خطأ في مسح شبكة {network}: {e}")
|
| 537 |
return [], []
|
| 538 |
|
| 539 |
async def get_general_whale_activity(self):
|
| 540 |
try:
|
| 541 |
-
print("🔍 بدء مسح نشاط الحيتان العام...")
|
| 542 |
tasks = []
|
| 543 |
networks_to_scan = ['ethereum', 'bsc']
|
| 544 |
for network in networks_to_scan:
|
|
@@ -556,9 +548,8 @@ class EnhancedWhaleMonitor:
|
|
| 556 |
all_alerts.extend(alerts)
|
| 557 |
all_signals.extend(signals)
|
| 558 |
successful_networks += 1
|
| 559 |
-
print(f"✅ شبكة {networks_to_scan[i]} تمت معالجتها بنجاح")
|
| 560 |
else:
|
| 561 |
-
|
| 562 |
|
| 563 |
all_alerts.sort(key=lambda x: x['timestamp'], reverse=True)
|
| 564 |
|
|
@@ -591,13 +582,11 @@ class EnhancedWhaleMonitor:
|
|
| 591 |
'flow_direction': 'BALANCED'
|
| 592 |
}
|
| 593 |
}
|
| 594 |
-
print("ℹ️ لم يتم اكتشاف أي نشاط حيتان كبير")
|
| 595 |
return result
|
| 596 |
|
| 597 |
latest_alert = all_alerts[0] if all_alerts else None
|
| 598 |
latest_time_info = f"آخر نشاط منذ {latest_alert['minutes_ago']:.1f} دقيقة" if latest_alert else ""
|
| 599 |
|
| 600 |
-
# تحديد flow_direction بشكل آمن
|
| 601 |
flow_direction = 'BALANCED'
|
| 602 |
if net_exchange_flow < 0:
|
| 603 |
flow_direction = 'TO_EXCHANGES'
|
|
@@ -668,8 +657,6 @@ class EnhancedWhaleMonitor:
|
|
| 668 |
|
| 669 |
except Exception as e:
|
| 670 |
print(f"❌ فشل مراقبة الحيتان العامة: {e}")
|
| 671 |
-
import traceback
|
| 672 |
-
traceback.print_exc()
|
| 673 |
return {
|
| 674 |
'data_available': False,
|
| 675 |
'description': f'غير متوفر - فشل في مراقبة الحيتان: {str(e)}',
|
|
@@ -683,12 +670,11 @@ class EnhancedWhaleMonitor:
|
|
| 683 |
}
|
| 684 |
|
| 685 |
async def _call_rpc_async(self, network, method, params=[]):
|
| 686 |
-
max_retries =
|
| 687 |
|
| 688 |
for attempt in range(max_retries):
|
| 689 |
endpoint = self._get_next_rpc_endpoint(network)
|
| 690 |
if not endpoint:
|
| 691 |
-
print(f"❌ لا توجد نقاط نهاية متاحة لـ {network}")
|
| 692 |
return None
|
| 693 |
|
| 694 |
try:
|
|
@@ -700,18 +686,16 @@ class EnhancedWhaleMonitor:
|
|
| 700 |
|
| 701 |
payload = {"jsonrpc": "2.0", "method": method, "params": params, "id": 1}
|
| 702 |
|
| 703 |
-
timeout =
|
| 704 |
|
| 705 |
async with httpx.AsyncClient(timeout=timeout) as client:
|
| 706 |
response = await client.post(endpoint, json=payload)
|
| 707 |
|
| 708 |
if response.status_code == 401:
|
| 709 |
-
print(f"❌ خطأ في المصادقة لنقطة النهاية {endpoint}")
|
| 710 |
self._remove_rpc_endpoint(network, endpoint)
|
| 711 |
continue
|
| 712 |
elif response.status_code == 429:
|
| 713 |
-
|
| 714 |
-
await asyncio.sleep(2 * (attempt + 1))
|
| 715 |
continue
|
| 716 |
|
| 717 |
response.raise_for_status()
|
|
@@ -720,27 +704,12 @@ class EnhancedWhaleMonitor:
|
|
| 720 |
self.rpc_failures[network] = 0
|
| 721 |
return result
|
| 722 |
|
| 723 |
-
except httpx.HTTPStatusError as e:
|
| 724 |
-
if e.response.status_code == 429:
|
| 725 |
-
self.rpc_failures[network] += 1
|
| 726 |
-
await asyncio.sleep(3 * (attempt + 1))
|
| 727 |
-
continue
|
| 728 |
-
elif e.response.status_code == 401:
|
| 729 |
-
self.rpc_failures[network] += 1
|
| 730 |
-
self._remove_rpc_endpoint(network, endpoint)
|
| 731 |
-
continue
|
| 732 |
-
else:
|
| 733 |
-
self.rpc_failures[network] += 1
|
| 734 |
-
print(f"❌ خطأ HTTP في RPC {network}: {e}")
|
| 735 |
-
|
| 736 |
except Exception as e:
|
| 737 |
self.rpc_failures[network] += 1
|
| 738 |
-
print(f"❌ خطأ عام في RPC {network}: {e}")
|
| 739 |
|
| 740 |
if attempt < max_retries - 1:
|
| 741 |
-
await asyncio.sleep(
|
| 742 |
|
| 743 |
-
print(f"❌ فشل جميع محاولات RPC لـ {network}")
|
| 744 |
return None
|
| 745 |
|
| 746 |
def _get_next_rpc_endpoint(self, network):
|
|
@@ -760,7 +729,6 @@ class EnhancedWhaleMonitor:
|
|
| 760 |
def _remove_rpc_endpoint(self, network, endpoint):
|
| 761 |
if network in self.rpc_endpoints and endpoint in self.rpc_endpoints[network]:
|
| 762 |
self.rpc_endpoints[network].remove(endpoint)
|
| 763 |
-
print(f"⚠️ تمت إزالة نقطة RPC {endpoint} من {network} بسبب الفشل")
|
| 764 |
|
| 765 |
if self.current_rpc_index[network] >= len(self.rpc_endpoints[network]):
|
| 766 |
self.current_rpc_index[network] = 0
|
|
@@ -803,68 +771,53 @@ class EnhancedWhaleMonitor:
|
|
| 803 |
if api_name == 'etherscan':
|
| 804 |
if stats['requests_per_second'] > 4:
|
| 805 |
delay = 0.2 * (stats['requests_per_second'] - 4)
|
| 806 |
-
print(f"⏰ تأخير {delay:.2f} ثانية لتجنب حد المعدل لـ {api_name}")
|
| 807 |
await asyncio.sleep(delay)
|
| 808 |
|
| 809 |
if stats['requests_today'] > 95000:
|
| 810 |
-
print(f"🚨 تجاوز الحد اليومي لـ {api_name}")
|
| 811 |
return True
|
| 812 |
|
| 813 |
elif api_name == 'infura':
|
| 814 |
if stats['requests_per_second'] > 90:
|
| 815 |
delay = 0.1 * (stats['requests_per_second'] - 90)
|
| 816 |
-
print(f"⏰ تأخير {delay:.2f} ثانية لتجنب حد المعدل لـ {api_name}")
|
| 817 |
await asyncio.sleep(delay)
|
| 818 |
|
| 819 |
if stats['requests_today'] > 99000:
|
| 820 |
-
print(f"🚨 تجاوز الحد اليومي لـ {api_name}")
|
| 821 |
return True
|
| 822 |
|
| 823 |
elif api_name == 'moralis':
|
| 824 |
if stats['requests_per_second'] > 4:
|
| 825 |
delay = 0.2 * (stats['requests_per_second'] - 4)
|
| 826 |
-
print(f"⏰ تأخير {delay:.2f} ثانية لتجنب حد المعدل لـ {api_name}")
|
| 827 |
await asyncio.sleep(delay)
|
| 828 |
|
| 829 |
if stats['requests_today'] > 95000:
|
| 830 |
-
print(f"🚨 تجاوز الحد اليومي لـ {api_name}")
|
| 831 |
return True
|
| 832 |
|
| 833 |
return False
|
| 834 |
|
| 835 |
async def _find_contract_address_enhanced(self, symbol):
|
| 836 |
-
"""بحث محسن عن عنوان العقد باستخدام مصادر متعددة"""
|
| 837 |
base_symbol = symbol.split("/")[0] if '/' in symbol else symbol
|
| 838 |
symbol_lower = base_symbol.lower()
|
| 839 |
|
| 840 |
-
# البحث في الكاش أولاً
|
| 841 |
if symbol_lower in self.contract_cache:
|
| 842 |
return self.contract_cache[symbol_lower]
|
| 843 |
|
| 844 |
-
# البحث في قاعدة البيانات المحلية
|
| 845 |
for key, address in self.contracts_db.items():
|
| 846 |
if symbol_lower in key.lower():
|
| 847 |
self.contract_cache[symbol_lower] = address
|
| 848 |
-
print(f"✅ تم العثور على عنوان العقد لـ {symbol} في قاعدة البيانات: {address}")
|
| 849 |
return address
|
| 850 |
|
| 851 |
-
# البحث عبر CoinGecko
|
| 852 |
coingecko_address = await self._find_contract_via_coingecko(base_symbol)
|
| 853 |
if coingecko_address:
|
| 854 |
self.contract_cache[symbol_lower] = coingecko_address
|
| 855 |
-
# حفظ في قاعدة البيانات للمستقبل
|
| 856 |
self.contracts_db[symbol_lower] = coingecko_address
|
| 857 |
if self.r2_service:
|
| 858 |
await self._save_contracts_to_r2()
|
| 859 |
return coingecko_address
|
| 860 |
|
| 861 |
-
print(f"⚠️ لم يتم العثور على عنوان عقد لـ {symbol} في أي مصدر")
|
| 862 |
return None
|
| 863 |
|
| 864 |
async def _find_contract_via_coingecko(self, symbol):
|
| 865 |
-
"""البحث عن عنوان العقد عبر CoinGecko API"""
|
| 866 |
try:
|
| 867 |
-
# البحث عن ID العملة أولاً
|
| 868 |
search_url = f"https://api.coingecko.com/api/v3/search?query={symbol}"
|
| 869 |
async with httpx.AsyncClient(timeout=15) as client:
|
| 870 |
response = await client.get(search_url)
|
|
@@ -877,31 +830,24 @@ class EnhancedWhaleMonitor:
|
|
| 877 |
if not coins:
|
| 878 |
return None
|
| 879 |
|
| 880 |
-
# أخذ أفضل نتيجة مطابقة
|
| 881 |
best_coin = coins[0]
|
| 882 |
coin_id = best_coin.get('id')
|
| 883 |
|
| 884 |
if not coin_id:
|
| 885 |
return None
|
| 886 |
|
| 887 |
-
# جلب تفاصيل العملة
|
| 888 |
detail_url = f"https://api.coingecko.com/api/v3/coins/{coin_id}"
|
| 889 |
detail_response = await client.get(detail_url)
|
| 890 |
if detail_response.status_code != 200:
|
| 891 |
return None
|
| 892 |
|
| 893 |
detail_data = detail_response.json()
|
| 894 |
-
|
| 895 |
-
# البحث عن عناوين العقود في المنصات المختلفة
|
| 896 |
platforms = detail_data.get('platforms', {})
|
| 897 |
|
| 898 |
-
# إعطاء الأولوية لـ Ethereum ثم BSC ثم Polygon
|
| 899 |
for platform in ['ethereum', 'binance-smart-chain', 'polygon-pos']:
|
| 900 |
if platform in platforms and platforms[platform]:
|
| 901 |
address = platforms[platform]
|
| 902 |
-
if address and len(address) == 42:
|
| 903 |
-
print(f"✅ تم العثور على عنوان {symbol} على {platform}: {address}")
|
| 904 |
-
# تحديد الشبكة المناسبة للرمز
|
| 905 |
network_map = {
|
| 906 |
'ethereum': 'ethereum',
|
| 907 |
'binance-smart-chain': 'bsc',
|
|
@@ -913,51 +859,40 @@ class EnhancedWhaleMonitor:
|
|
| 913 |
return None
|
| 914 |
|
| 915 |
except Exception as e:
|
| 916 |
-
print(f"❌ فشل البحث عن عقد {symbol} عبر CoinGecko: {e}")
|
| 917 |
return None
|
| 918 |
|
| 919 |
async def _save_contracts_to_r2(self):
|
| 920 |
-
"""حفظ قاعدة بيانات العقود إلى R2"""
|
| 921 |
try:
|
| 922 |
key = "contracts_database.json"
|
| 923 |
data_json = json.dumps(self.contracts_db, indent=2).encode('utf-8')
|
| 924 |
self.r2_service.s3_client.put_object(
|
| 925 |
Bucket="trading", Key=key, Body=data_json, ContentType="application/json"
|
| 926 |
)
|
| 927 |
-
print(f"✅ تم حفظ {len(self.contracts_db)} عقد في R2")
|
| 928 |
except Exception as e:
|
| 929 |
-
|
| 930 |
|
| 931 |
async def get_symbol_specific_whale_data(self, symbol, contract_address=None):
|
| 932 |
try:
|
| 933 |
-
print(f"🔍 جلب بيانات الحيتان لـ {symbol}...")
|
| 934 |
base_symbol = symbol.split("/")[0] if '/' in symbol else symbol
|
| 935 |
|
| 936 |
-
# البحث عن عنوان العقد إذا لم يتم توفيره
|
| 937 |
if not contract_address:
|
| 938 |
contract_address = await self._find_contract_address_enhanced(base_symbol)
|
| 939 |
|
| 940 |
if not contract_address:
|
| 941 |
-
print(f"⚠️ لم يتم العثور على عنوان عقد لـ {symbol}")
|
| 942 |
return await self._scan_networks_for_symbol(symbol, base_symbol)
|
| 943 |
|
| 944 |
-
# تحديد الشبكة المناسبة للبحث
|
| 945 |
network = self.symbol_networks.get(base_symbol, 'ethereum')
|
| 946 |
-
print(f"🔍 البحث في شبكة {network} للرمز {symbol}")
|
| 947 |
|
| 948 |
api_data = await self._get_combined_api_data(contract_address, network)
|
| 949 |
|
| 950 |
if api_data:
|
| 951 |
enriched_data = await self._enrich_api_data_with_timing(api_data)
|
| 952 |
result = self._analyze_symbol_specific_data(enriched_data, symbol)
|
| 953 |
-
print(f"✅ تم تحليل بيانات الحيتان لـ {symbol}: {result.get('transfer_count', 0)} تحويل")
|
| 954 |
return result
|
| 955 |
else:
|
| 956 |
-
print(f"⚠️ لا توجد بيانات API لـ {symbol}")
|
| 957 |
return await self._scan_networks_for_symbol(symbol, base_symbol)
|
| 958 |
|
| 959 |
except Exception as e:
|
| 960 |
-
print(f"❌ فشل جلب بيانات الحيتان لـ {symbol}: {e}")
|
| 961 |
return {
|
| 962 |
'data_available': False,
|
| 963 |
'description': f'غير متوفر - خطأ في جلب بيانات الحيتان',
|
|
@@ -969,7 +904,6 @@ class EnhancedWhaleMonitor:
|
|
| 969 |
async def _get_combined_api_data(self, contract_address, network='ethereum'):
|
| 970 |
tasks = []
|
| 971 |
|
| 972 |
-
# ��ستخدام APIs المتاحة فقط
|
| 973 |
if self.moralis_key and self._is_valid_moralis_key():
|
| 974 |
tasks.append(self._get_moralis_token_data(contract_address, network))
|
| 975 |
|
|
@@ -977,7 +911,6 @@ class EnhancedWhaleMonitor:
|
|
| 977 |
tasks.append(self._get_etherscan_token_data_v2(contract_address))
|
| 978 |
|
| 979 |
if not tasks:
|
| 980 |
-
print("⚠️ لا توجد مفاتيح API متاحة لجلب بيانات الرموز")
|
| 981 |
return await self._get_rpc_token_data(contract_address, network)
|
| 982 |
|
| 983 |
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
@@ -988,25 +921,19 @@ class EnhancedWhaleMonitor:
|
|
| 988 |
all_transfers.extend(res)
|
| 989 |
|
| 990 |
if not all_transfers:
|
| 991 |
-
print("⚠️ فشل جميع APIs، استخدام RPC كبديل")
|
| 992 |
return await self._get_rpc_token_data(contract_address, network)
|
| 993 |
|
| 994 |
-
print(f"✅ تم جمع {len(all_transfers)} تحويل من APIs")
|
| 995 |
return all_transfers
|
| 996 |
|
| 997 |
def _is_valid_moralis_key(self):
|
| 998 |
-
"""التحقق من أن مفتاح Moralis صالح"""
|
| 999 |
if not self.moralis_key:
|
| 1000 |
return False
|
| 1001 |
return len(self.moralis_key) >= 20
|
| 1002 |
|
| 1003 |
async def _get_rpc_token_data(self, contract_address, network='ethereum'):
|
| 1004 |
try:
|
| 1005 |
-
print(f"🔍 استخدام RPC لجلب بيانات العقد {contract_address} على شبكة {network}")
|
| 1006 |
-
|
| 1007 |
endpoint = self._get_next_rpc_endpoint(network)
|
| 1008 |
if not endpoint:
|
| 1009 |
-
print(f"❌ لا توجد نقاط RPC متاحة لـ {network}")
|
| 1010 |
return []
|
| 1011 |
|
| 1012 |
transfers = []
|
|
@@ -1023,8 +950,7 @@ class EnhancedWhaleMonitor:
|
|
| 1023 |
|
| 1024 |
latest_block = int(result, 16)
|
| 1025 |
|
| 1026 |
-
|
| 1027 |
-
for block_offset in range(50):
|
| 1028 |
block_number = latest_block - block_offset
|
| 1029 |
if block_number < 0:
|
| 1030 |
break
|
|
@@ -1050,16 +976,13 @@ class EnhancedWhaleMonitor:
|
|
| 1050 |
'blockNumber': str(block_number)
|
| 1051 |
})
|
| 1052 |
|
| 1053 |
-
print(f"✅ تم جمع {len(transfers)} تحويل من RPC")
|
| 1054 |
return transfers
|
| 1055 |
|
| 1056 |
except Exception as e:
|
| 1057 |
-
print(f"❌ فشل جلب بيانات RPC: {e}")
|
| 1058 |
return []
|
| 1059 |
|
| 1060 |
async def _get_etherscan_token_data_v2(self, contract_address):
|
| 1061 |
if not self.etherscan_key:
|
| 1062 |
-
print("⚠️ مفتاح Etherscan غير متوفر")
|
| 1063 |
return []
|
| 1064 |
|
| 1065 |
try:
|
|
@@ -1083,38 +1006,26 @@ class EnhancedWhaleMonitor:
|
|
| 1083 |
response = await client.get(base_url, params=params)
|
| 1084 |
|
| 1085 |
if response.status_code == 429:
|
| 1086 |
-
print("⏰ تجاوز حد المعدل في Etherscan")
|
| 1087 |
await asyncio.sleep(2)
|
| 1088 |
return []
|
| 1089 |
elif response.status_code == 401:
|
| 1090 |
-
print("❌ خطأ في مصادقة Etherscan - المفتاح غير صالح")
|
| 1091 |
return []
|
| 1092 |
elif response.status_code >= 500:
|
| 1093 |
-
print(f"❌ خطأ في خادم Etherscan: {response.status_code}")
|
| 1094 |
return []
|
| 1095 |
|
| 1096 |
response.raise_for_status()
|
| 1097 |
data = response.json()
|
| 1098 |
|
| 1099 |
if data.get('status') == '1' and data.get('message') == 'OK':
|
| 1100 |
-
|
| 1101 |
-
print(f"✅ تم جلب {len(result)} تحويل من Etherscan")
|
| 1102 |
-
return result
|
| 1103 |
else:
|
| 1104 |
-
error_msg = data.get('result', data.get('message', 'Unknown error'))
|
| 1105 |
-
print(f"⚠️ استجابة غير متوقعة من Etherscan: {error_msg}")
|
| 1106 |
return []
|
| 1107 |
|
| 1108 |
-
except httpx.HTTPStatusError as e:
|
| 1109 |
-
print(f"❌ خطأ HTTP في Etherscan: {e}")
|
| 1110 |
-
return []
|
| 1111 |
except Exception as e:
|
| 1112 |
-
print(f"❌ خطأ عام في Etherscan: {e}")
|
| 1113 |
return []
|
| 1114 |
|
| 1115 |
async def _get_moralis_token_data(self, contract_address, network='ethereum'):
|
| 1116 |
if not self.moralis_key or not self._is_valid_moralis_key():
|
| 1117 |
-
print("⚠️ مفتاح Moralis غير متوفر أو غير صالح")
|
| 1118 |
return []
|
| 1119 |
|
| 1120 |
try:
|
|
@@ -1131,7 +1042,6 @@ class EnhancedWhaleMonitor:
|
|
| 1131 |
|
| 1132 |
chain_id = chains.get(network)
|
| 1133 |
if not chain_id:
|
| 1134 |
-
print(f"⚠️ شبكة {network} غير مدعومة في Moralis")
|
| 1135 |
return []
|
| 1136 |
|
| 1137 |
all_transfers = []
|
|
@@ -1153,25 +1063,19 @@ class EnhancedWhaleMonitor:
|
|
| 1153 |
if response.status_code == 200:
|
| 1154 |
result = response.json().get('result', [])
|
| 1155 |
all_transfers.extend(result)
|
| 1156 |
-
print(f"✅ تم جلب {len(result)} تحويل من Moralis ({network})")
|
| 1157 |
elif response.status_code == 401:
|
| 1158 |
-
print(f"❌ خطأ في مصادقة Moralis - المفتاح غير صالح")
|
| 1159 |
return []
|
| 1160 |
elif response.status_code == 404:
|
| 1161 |
-
print(f"⚠️ لم يتم العثور على بيانات للعقد {contract_address} على {network}")
|
| 1162 |
return []
|
| 1163 |
else:
|
| 1164 |
-
print(f"⚠️ استجابة غير ناجحة من Moralis لـ {network}: {response.status_code}")
|
| 1165 |
return []
|
| 1166 |
|
| 1167 |
except Exception as chain_error:
|
| 1168 |
-
print(f"❌ خطأ في Moralis لـ {network}: {chain_error}")
|
| 1169 |
return []
|
| 1170 |
|
| 1171 |
return all_transfers
|
| 1172 |
|
| 1173 |
except Exception as e:
|
| 1174 |
-
print(f"❌ خطأ عام في Moralis: {e}")
|
| 1175 |
return []
|
| 1176 |
|
| 1177 |
async def _enrich_api_data_with_timing(self, api_data):
|
|
@@ -1211,7 +1115,6 @@ class EnhancedWhaleMonitor:
|
|
| 1211 |
enriched_data.append(enriched_transfer)
|
| 1212 |
|
| 1213 |
except Exception as e:
|
| 1214 |
-
print(f"⚠️ خطأ في توقيت البيانات: {e}")
|
| 1215 |
continue
|
| 1216 |
|
| 1217 |
return enriched_data
|
|
@@ -1232,11 +1135,11 @@ class EnhancedWhaleMonitor:
|
|
| 1232 |
|
| 1233 |
for transfer in enriched_data:
|
| 1234 |
value = float(transfer.get('value', 0))
|
| 1235 |
-
if value > 1e15:
|
| 1236 |
value = value / 1e18
|
| 1237 |
volumes.append(value)
|
| 1238 |
|
| 1239 |
-
if value > 10000:
|
| 1240 |
large_transfers.append(transfer)
|
| 1241 |
|
| 1242 |
total_volume = sum(volumes)
|
|
@@ -1275,7 +1178,6 @@ class EnhancedWhaleMonitor:
|
|
| 1275 |
}
|
| 1276 |
|
| 1277 |
except Exception as e:
|
| 1278 |
-
print(f"❌ خطأ في تحليل بيانات {symbol}: {e}")
|
| 1279 |
return {
|
| 1280 |
'data_available': False,
|
| 1281 |
'description': f'غير متوفر - خطأ في تحليل البيانات',
|
|
@@ -1292,7 +1194,6 @@ class EnhancedWhaleMonitor:
|
|
| 1292 |
if self.data_manager:
|
| 1293 |
native_price = await self.data_manager.get_native_coin_price(network)
|
| 1294 |
if native_price:
|
| 1295 |
-
print(f"✅ تم اكتشاف {symbol} على شبكة {network} - سعر {network.upper()}: ${native_price:.2f}")
|
| 1296 |
return {
|
| 1297 |
'data_available': True,
|
| 1298 |
'description': f'تم اكتشاف {symbol} على شبكة {network}',
|
|
@@ -1301,10 +1202,8 @@ class EnhancedWhaleMonitor:
|
|
| 1301 |
'source': 'network_scan'
|
| 1302 |
}
|
| 1303 |
except Exception as e:
|
| 1304 |
-
print(f"⚠️ فشل مسح شبكة {network} لـ {symbol}: {e}")
|
| 1305 |
continue
|
| 1306 |
|
| 1307 |
-
print(f"❌ لم يتم العثور على {symbol} على أي شبكة")
|
| 1308 |
return {
|
| 1309 |
'data_available': False,
|
| 1310 |
'description': f'غير متوفر - لم يتم العثور على {symbol} على أي شبكة',
|
|
@@ -1401,66 +1300,9 @@ class EnhancedWhaleMonitor:
|
|
| 1401 |
return signal
|
| 1402 |
|
| 1403 |
except Exception as e:
|
| 1404 |
-
print(f"❌ خطأ في توليد إشارة الحيتان: {e}")
|
| 1405 |
return None
|
| 1406 |
|
| 1407 |
-
async def track_pattern_outcome(self, symbol, pattern_analysis, success, profit_percent):
|
| 1408 |
-
if not pattern_analysis:
|
| 1409 |
-
return
|
| 1410 |
-
|
| 1411 |
-
pattern_name = pattern_analysis.get('pattern_detected')
|
| 1412 |
-
confidence = pattern_analysis.get('pattern_confidence', 0)
|
| 1413 |
-
|
| 1414 |
-
if pattern_name not in ['no_clear_pattern', 'insufficient_data']:
|
| 1415 |
-
if pattern_name not in self.pattern_performance:
|
| 1416 |
-
self.pattern_performance[pattern_name] = {
|
| 1417 |
-
'total_trades': 0,
|
| 1418 |
-
'successful_trades': 0,
|
| 1419 |
-
'total_profit': 0,
|
| 1420 |
-
'total_confidence': 0,
|
| 1421 |
-
'last_updated': datetime.now().isoformat()
|
| 1422 |
-
}
|
| 1423 |
-
|
| 1424 |
-
stats = self.pattern_performance[pattern_name]
|
| 1425 |
-
stats['total_trades'] += 1
|
| 1426 |
-
stats['total_confidence'] += confidence
|
| 1427 |
-
stats['last_updated'] = datetime.now().isoformat()
|
| 1428 |
-
|
| 1429 |
-
if success:
|
| 1430 |
-
stats['successful_trades'] += 1
|
| 1431 |
-
stats['total_profit'] += profit_percent
|
| 1432 |
-
|
| 1433 |
-
if stats['total_trades'] > 0:
|
| 1434 |
-
self.pattern_success_rates[pattern_name] = {
|
| 1435 |
-
'success_rate': stats['successful_trades'] / stats['total_trades'],
|
| 1436 |
-
'avg_profit': stats['total_profit'] / stats['total_trades'],
|
| 1437 |
-
'total_trades': stats['total_trades'],
|
| 1438 |
-
'last_updated': datetime.now().isoformat()
|
| 1439 |
-
}
|
| 1440 |
-
|
| 1441 |
-
if stats['total_trades'] % 10 == 0:
|
| 1442 |
-
await self._save_pattern_performance()
|
| 1443 |
-
|
| 1444 |
-
print(f"✅ تم تحديث أداء النمط {pattern_name}: {stats['successful_trades']}/{stats['total_trades']} نجاح")
|
| 1445 |
-
|
| 1446 |
-
def get_pattern_reliability(self, pattern_name):
|
| 1447 |
-
if pattern_name in self.pattern_success_rates:
|
| 1448 |
-
return self.pattern_success_rates[pattern_name]['success_rate']
|
| 1449 |
-
return 0.5
|
| 1450 |
-
|
| 1451 |
-
async def save_all_pattern_data(self):
|
| 1452 |
-
await self._save_pattern_performance()
|
| 1453 |
-
|
| 1454 |
-
def get_pattern_performance_summary(self):
|
| 1455 |
-
return {
|
| 1456 |
-
'pattern_performance': self.pattern_performance,
|
| 1457 |
-
'pattern_success_rates': self.pattern_success_rates,
|
| 1458 |
-
'total_patterns_tracked': len(self.pattern_performance),
|
| 1459 |
-
'last_updated': datetime.now().isoformat()
|
| 1460 |
-
}
|
| 1461 |
-
|
| 1462 |
async def cleanup(self):
|
| 1463 |
await self.http_client.aclose()
|
| 1464 |
-
print("✅ تم تنظيف موارد EnhancedWhaleMonitor")
|
| 1465 |
|
| 1466 |
print("✅ EnhancedWhaleMonitor loaded - Real-time whale activity monitoring ready")
|
|
|
|
| 8 |
from collections import defaultdict, deque
|
| 9 |
import ccxt.pro as ccxt
|
| 10 |
import numpy as np
|
| 11 |
+
import logging
|
| 12 |
+
|
| 13 |
+
# تعطيل تسجيل HTTP المزعج
|
| 14 |
+
logging.getLogger("httpx").setLevel(logging.WARNING)
|
| 15 |
+
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
| 16 |
|
| 17 |
class EnhancedWhaleMonitor:
|
| 18 |
def __init__(self, contracts_db=None, r2_service=None):
|
| 19 |
+
# تعطيل التسجيل التفصيلي لـ HTTP Client
|
| 20 |
+
self.http_client = httpx.AsyncClient(
|
| 21 |
+
timeout=15.0,
|
| 22 |
+
limits=httpx.Limits(max_connections=50, max_keepalive_connections=10),
|
| 23 |
+
follow_redirects=True
|
| 24 |
+
)
|
| 25 |
|
| 26 |
self.moralis_key = os.getenv("MORALIS_KEY")
|
| 27 |
self.etherscan_key = os.getenv("ETHERSCAN_KEY")
|
|
|
|
| 109 |
self.price_cache = {}
|
| 110 |
self.last_scan_time = {}
|
| 111 |
|
| 112 |
+
self.contract_cache = {}
|
| 113 |
+
self.symbol_networks = {}
|
| 114 |
|
| 115 |
self.pattern_performance = {}
|
| 116 |
self.pattern_success_rates = {}
|
|
|
|
| 118 |
if self.r2_service:
|
| 119 |
asyncio.create_task(self._load_pattern_performance())
|
| 120 |
|
|
|
|
| 121 |
if self.r2_service:
|
| 122 |
asyncio.create_task(self._load_contracts_from_r2())
|
| 123 |
|
|
|
|
| 168 |
response = self.r2_service.s3_client.get_object(Bucket="trading", Key=key)
|
| 169 |
contracts_data = json.loads(response['Body'].read())
|
| 170 |
|
|
|
|
| 171 |
self.contracts_db.update(contracts_data)
|
| 172 |
print(f"✅ تم تحميل {len(contracts_data)} عقد من R2")
|
| 173 |
|
|
|
|
| 203 |
|
| 204 |
async def _load_pattern_performance(self):
|
| 205 |
if not self.r2_service:
|
|
|
|
| 206 |
return
|
| 207 |
|
| 208 |
try:
|
|
|
|
| 211 |
data = json.loads(response['Body'].read())
|
| 212 |
self.pattern_performance = data.get('pattern_performance', {})
|
| 213 |
self.pattern_success_rates = data.get('pattern_success_rates', {})
|
|
|
|
| 214 |
except Exception as e:
|
|
|
|
| 215 |
self.pattern_performance = {}
|
| 216 |
self.pattern_success_rates = {}
|
| 217 |
|
| 218 |
async def _save_pattern_performance(self):
|
| 219 |
if not self.r2_service:
|
|
|
|
| 220 |
return
|
| 221 |
|
| 222 |
try:
|
|
|
|
| 232 |
self.r2_service.s3_client.put_object(
|
| 233 |
Bucket="trading", Key=key, Body=data_json, ContentType="application/json"
|
| 234 |
)
|
|
|
|
| 235 |
except Exception as e:
|
| 236 |
print(f"❌ فشل حفظ بيانات أداء الأنماط: {e}")
|
| 237 |
|
|
|
|
| 265 |
self.address_categories['unknown'].add(address_lower)
|
| 266 |
return 'unknown'
|
| 267 |
except Exception as e:
|
|
|
|
| 268 |
return 'unknown'
|
| 269 |
|
| 270 |
def _detect_exchange_pattern(self, transactions):
|
|
|
|
| 302 |
address_lower in self.address_categories['exchange'] or
|
| 303 |
self.address_labels.get(address_lower) in ['cex', 'suspected_cex'])
|
| 304 |
except Exception as e:
|
|
|
|
| 305 |
return False
|
| 306 |
|
| 307 |
async def _update_netflow_metrics(self, network, token_symbol, from_address, to_address, value_usd, transaction_hash):
|
|
|
|
| 331 |
self.netflow_data[network][token_symbol]['netflow'].append(current_netflow)
|
| 332 |
|
| 333 |
except Exception as e:
|
| 334 |
+
pass
|
| 335 |
|
| 336 |
def _initialize_token_metrics(self, network, token_symbol):
|
| 337 |
self.netflow_data[network][token_symbol] = {
|
|
|
|
| 371 |
return zscore
|
| 372 |
|
| 373 |
except Exception as e:
|
|
|
|
| 374 |
return 0
|
| 375 |
|
| 376 |
def _generate_netflow_signal(self, network, token_symbol):
|
|
|
|
| 445 |
return signal
|
| 446 |
|
| 447 |
except Exception as e:
|
|
|
|
| 448 |
return None
|
| 449 |
|
| 450 |
async def _scan_single_evm_network(self, network):
|
|
|
|
| 453 |
|
| 454 |
try:
|
| 455 |
if not self.data_manager:
|
|
|
|
| 456 |
return [], []
|
| 457 |
|
| 458 |
price_usd = await self.data_manager.get_native_coin_price(network)
|
| 459 |
if price_usd is None:
|
|
|
|
| 460 |
return [], []
|
| 461 |
|
| 462 |
latest_block_hex = await self._call_rpc_async(network, 'eth_blockNumber')
|
| 463 |
if not latest_block_hex:
|
|
|
|
| 464 |
return [], []
|
| 465 |
|
| 466 |
latest_block = int(latest_block_hex, 16)
|
|
|
|
| 518 |
})
|
| 519 |
whale_transactions_found += 1
|
| 520 |
|
| 521 |
+
await asyncio.sleep(0.05)
|
|
|
|
| 522 |
|
| 523 |
signal = self._generate_netflow_signal(network, 'NATIVE')
|
| 524 |
if signal:
|
| 525 |
trading_signals.append(signal)
|
| 526 |
|
|
|
|
| 527 |
return whale_alerts, trading_signals
|
| 528 |
|
| 529 |
except Exception as e:
|
|
|
|
| 530 |
return [], []
|
| 531 |
|
| 532 |
async def get_general_whale_activity(self):
|
| 533 |
try:
|
|
|
|
| 534 |
tasks = []
|
| 535 |
networks_to_scan = ['ethereum', 'bsc']
|
| 536 |
for network in networks_to_scan:
|
|
|
|
| 548 |
all_alerts.extend(alerts)
|
| 549 |
all_signals.extend(signals)
|
| 550 |
successful_networks += 1
|
|
|
|
| 551 |
else:
|
| 552 |
+
pass
|
| 553 |
|
| 554 |
all_alerts.sort(key=lambda x: x['timestamp'], reverse=True)
|
| 555 |
|
|
|
|
| 582 |
'flow_direction': 'BALANCED'
|
| 583 |
}
|
| 584 |
}
|
|
|
|
| 585 |
return result
|
| 586 |
|
| 587 |
latest_alert = all_alerts[0] if all_alerts else None
|
| 588 |
latest_time_info = f"آخر نشاط منذ {latest_alert['minutes_ago']:.1f} دقيقة" if latest_alert else ""
|
| 589 |
|
|
|
|
| 590 |
flow_direction = 'BALANCED'
|
| 591 |
if net_exchange_flow < 0:
|
| 592 |
flow_direction = 'TO_EXCHANGES'
|
|
|
|
| 657 |
|
| 658 |
except Exception as e:
|
| 659 |
print(f"❌ فشل مراقبة الحيتان العامة: {e}")
|
|
|
|
|
|
|
| 660 |
return {
|
| 661 |
'data_available': False,
|
| 662 |
'description': f'غير متوفر - فشل في مراقبة الحيتان: {str(e)}',
|
|
|
|
| 670 |
}
|
| 671 |
|
| 672 |
async def _call_rpc_async(self, network, method, params=[]):
|
| 673 |
+
max_retries = 2
|
| 674 |
|
| 675 |
for attempt in range(max_retries):
|
| 676 |
endpoint = self._get_next_rpc_endpoint(network)
|
| 677 |
if not endpoint:
|
|
|
|
| 678 |
return None
|
| 679 |
|
| 680 |
try:
|
|
|
|
| 686 |
|
| 687 |
payload = {"jsonrpc": "2.0", "method": method, "params": params, "id": 1}
|
| 688 |
|
| 689 |
+
timeout = 20.0
|
| 690 |
|
| 691 |
async with httpx.AsyncClient(timeout=timeout) as client:
|
| 692 |
response = await client.post(endpoint, json=payload)
|
| 693 |
|
| 694 |
if response.status_code == 401:
|
|
|
|
| 695 |
self._remove_rpc_endpoint(network, endpoint)
|
| 696 |
continue
|
| 697 |
elif response.status_code == 429:
|
| 698 |
+
await asyncio.sleep(1 * (attempt + 1))
|
|
|
|
| 699 |
continue
|
| 700 |
|
| 701 |
response.raise_for_status()
|
|
|
|
| 704 |
self.rpc_failures[network] = 0
|
| 705 |
return result
|
| 706 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 707 |
except Exception as e:
|
| 708 |
self.rpc_failures[network] += 1
|
|
|
|
| 709 |
|
| 710 |
if attempt < max_retries - 1:
|
| 711 |
+
await asyncio.sleep(0.5)
|
| 712 |
|
|
|
|
| 713 |
return None
|
| 714 |
|
| 715 |
def _get_next_rpc_endpoint(self, network):
|
|
|
|
| 729 |
def _remove_rpc_endpoint(self, network, endpoint):
|
| 730 |
if network in self.rpc_endpoints and endpoint in self.rpc_endpoints[network]:
|
| 731 |
self.rpc_endpoints[network].remove(endpoint)
|
|
|
|
| 732 |
|
| 733 |
if self.current_rpc_index[network] >= len(self.rpc_endpoints[network]):
|
| 734 |
self.current_rpc_index[network] = 0
|
|
|
|
| 771 |
if api_name == 'etherscan':
|
| 772 |
if stats['requests_per_second'] > 4:
|
| 773 |
delay = 0.2 * (stats['requests_per_second'] - 4)
|
|
|
|
| 774 |
await asyncio.sleep(delay)
|
| 775 |
|
| 776 |
if stats['requests_today'] > 95000:
|
|
|
|
| 777 |
return True
|
| 778 |
|
| 779 |
elif api_name == 'infura':
|
| 780 |
if stats['requests_per_second'] > 90:
|
| 781 |
delay = 0.1 * (stats['requests_per_second'] - 90)
|
|
|
|
| 782 |
await asyncio.sleep(delay)
|
| 783 |
|
| 784 |
if stats['requests_today'] > 99000:
|
|
|
|
| 785 |
return True
|
| 786 |
|
| 787 |
elif api_name == 'moralis':
|
| 788 |
if stats['requests_per_second'] > 4:
|
| 789 |
delay = 0.2 * (stats['requests_per_second'] - 4)
|
|
|
|
| 790 |
await asyncio.sleep(delay)
|
| 791 |
|
| 792 |
if stats['requests_today'] > 95000:
|
|
|
|
| 793 |
return True
|
| 794 |
|
| 795 |
return False
|
| 796 |
|
| 797 |
async def _find_contract_address_enhanced(self, symbol):
|
|
|
|
| 798 |
base_symbol = symbol.split("/")[0] if '/' in symbol else symbol
|
| 799 |
symbol_lower = base_symbol.lower()
|
| 800 |
|
|
|
|
| 801 |
if symbol_lower in self.contract_cache:
|
| 802 |
return self.contract_cache[symbol_lower]
|
| 803 |
|
|
|
|
| 804 |
for key, address in self.contracts_db.items():
|
| 805 |
if symbol_lower in key.lower():
|
| 806 |
self.contract_cache[symbol_lower] = address
|
|
|
|
| 807 |
return address
|
| 808 |
|
|
|
|
| 809 |
coingecko_address = await self._find_contract_via_coingecko(base_symbol)
|
| 810 |
if coingecko_address:
|
| 811 |
self.contract_cache[symbol_lower] = coingecko_address
|
|
|
|
| 812 |
self.contracts_db[symbol_lower] = coingecko_address
|
| 813 |
if self.r2_service:
|
| 814 |
await self._save_contracts_to_r2()
|
| 815 |
return coingecko_address
|
| 816 |
|
|
|
|
| 817 |
return None
|
| 818 |
|
| 819 |
async def _find_contract_via_coingecko(self, symbol):
|
|
|
|
| 820 |
try:
|
|
|
|
| 821 |
search_url = f"https://api.coingecko.com/api/v3/search?query={symbol}"
|
| 822 |
async with httpx.AsyncClient(timeout=15) as client:
|
| 823 |
response = await client.get(search_url)
|
|
|
|
| 830 |
if not coins:
|
| 831 |
return None
|
| 832 |
|
|
|
|
| 833 |
best_coin = coins[0]
|
| 834 |
coin_id = best_coin.get('id')
|
| 835 |
|
| 836 |
if not coin_id:
|
| 837 |
return None
|
| 838 |
|
|
|
|
| 839 |
detail_url = f"https://api.coingecko.com/api/v3/coins/{coin_id}"
|
| 840 |
detail_response = await client.get(detail_url)
|
| 841 |
if detail_response.status_code != 200:
|
| 842 |
return None
|
| 843 |
|
| 844 |
detail_data = detail_response.json()
|
|
|
|
|
|
|
| 845 |
platforms = detail_data.get('platforms', {})
|
| 846 |
|
|
|
|
| 847 |
for platform in ['ethereum', 'binance-smart-chain', 'polygon-pos']:
|
| 848 |
if platform in platforms and platforms[platform]:
|
| 849 |
address = platforms[platform]
|
| 850 |
+
if address and len(address) == 42:
|
|
|
|
|
|
|
| 851 |
network_map = {
|
| 852 |
'ethereum': 'ethereum',
|
| 853 |
'binance-smart-chain': 'bsc',
|
|
|
|
| 859 |
return None
|
| 860 |
|
| 861 |
except Exception as e:
|
|
|
|
| 862 |
return None
|
| 863 |
|
| 864 |
async def _save_contracts_to_r2(self):
|
|
|
|
| 865 |
try:
|
| 866 |
key = "contracts_database.json"
|
| 867 |
data_json = json.dumps(self.contracts_db, indent=2).encode('utf-8')
|
| 868 |
self.r2_service.s3_client.put_object(
|
| 869 |
Bucket="trading", Key=key, Body=data_json, ContentType="application/json"
|
| 870 |
)
|
|
|
|
| 871 |
except Exception as e:
|
| 872 |
+
pass
|
| 873 |
|
| 874 |
async def get_symbol_specific_whale_data(self, symbol, contract_address=None):
|
| 875 |
try:
|
|
|
|
| 876 |
base_symbol = symbol.split("/")[0] if '/' in symbol else symbol
|
| 877 |
|
|
|
|
| 878 |
if not contract_address:
|
| 879 |
contract_address = await self._find_contract_address_enhanced(base_symbol)
|
| 880 |
|
| 881 |
if not contract_address:
|
|
|
|
| 882 |
return await self._scan_networks_for_symbol(symbol, base_symbol)
|
| 883 |
|
|
|
|
| 884 |
network = self.symbol_networks.get(base_symbol, 'ethereum')
|
|
|
|
| 885 |
|
| 886 |
api_data = await self._get_combined_api_data(contract_address, network)
|
| 887 |
|
| 888 |
if api_data:
|
| 889 |
enriched_data = await self._enrich_api_data_with_timing(api_data)
|
| 890 |
result = self._analyze_symbol_specific_data(enriched_data, symbol)
|
|
|
|
| 891 |
return result
|
| 892 |
else:
|
|
|
|
| 893 |
return await self._scan_networks_for_symbol(symbol, base_symbol)
|
| 894 |
|
| 895 |
except Exception as e:
|
|
|
|
| 896 |
return {
|
| 897 |
'data_available': False,
|
| 898 |
'description': f'غير متوفر - خطأ في جلب بيانات الحيتان',
|
|
|
|
| 904 |
async def _get_combined_api_data(self, contract_address, network='ethereum'):
|
| 905 |
tasks = []
|
| 906 |
|
|
|
|
| 907 |
if self.moralis_key and self._is_valid_moralis_key():
|
| 908 |
tasks.append(self._get_moralis_token_data(contract_address, network))
|
| 909 |
|
|
|
|
| 911 |
tasks.append(self._get_etherscan_token_data_v2(contract_address))
|
| 912 |
|
| 913 |
if not tasks:
|
|
|
|
| 914 |
return await self._get_rpc_token_data(contract_address, network)
|
| 915 |
|
| 916 |
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
|
|
| 921 |
all_transfers.extend(res)
|
| 922 |
|
| 923 |
if not all_transfers:
|
|
|
|
| 924 |
return await self._get_rpc_token_data(contract_address, network)
|
| 925 |
|
|
|
|
| 926 |
return all_transfers
|
| 927 |
|
| 928 |
def _is_valid_moralis_key(self):
|
|
|
|
| 929 |
if not self.moralis_key:
|
| 930 |
return False
|
| 931 |
return len(self.moralis_key) >= 20
|
| 932 |
|
| 933 |
async def _get_rpc_token_data(self, contract_address, network='ethereum'):
|
| 934 |
try:
|
|
|
|
|
|
|
| 935 |
endpoint = self._get_next_rpc_endpoint(network)
|
| 936 |
if not endpoint:
|
|
|
|
| 937 |
return []
|
| 938 |
|
| 939 |
transfers = []
|
|
|
|
| 950 |
|
| 951 |
latest_block = int(result, 16)
|
| 952 |
|
| 953 |
+
for block_offset in range(20):
|
|
|
|
| 954 |
block_number = latest_block - block_offset
|
| 955 |
if block_number < 0:
|
| 956 |
break
|
|
|
|
| 976 |
'blockNumber': str(block_number)
|
| 977 |
})
|
| 978 |
|
|
|
|
| 979 |
return transfers
|
| 980 |
|
| 981 |
except Exception as e:
|
|
|
|
| 982 |
return []
|
| 983 |
|
| 984 |
async def _get_etherscan_token_data_v2(self, contract_address):
|
| 985 |
if not self.etherscan_key:
|
|
|
|
| 986 |
return []
|
| 987 |
|
| 988 |
try:
|
|
|
|
| 1006 |
response = await client.get(base_url, params=params)
|
| 1007 |
|
| 1008 |
if response.status_code == 429:
|
|
|
|
| 1009 |
await asyncio.sleep(2)
|
| 1010 |
return []
|
| 1011 |
elif response.status_code == 401:
|
|
|
|
| 1012 |
return []
|
| 1013 |
elif response.status_code >= 500:
|
|
|
|
| 1014 |
return []
|
| 1015 |
|
| 1016 |
response.raise_for_status()
|
| 1017 |
data = response.json()
|
| 1018 |
|
| 1019 |
if data.get('status') == '1' and data.get('message') == 'OK':
|
| 1020 |
+
return data.get('result', [])
|
|
|
|
|
|
|
| 1021 |
else:
|
|
|
|
|
|
|
| 1022 |
return []
|
| 1023 |
|
|
|
|
|
|
|
|
|
|
| 1024 |
except Exception as e:
|
|
|
|
| 1025 |
return []
|
| 1026 |
|
| 1027 |
async def _get_moralis_token_data(self, contract_address, network='ethereum'):
|
| 1028 |
if not self.moralis_key or not self._is_valid_moralis_key():
|
|
|
|
| 1029 |
return []
|
| 1030 |
|
| 1031 |
try:
|
|
|
|
| 1042 |
|
| 1043 |
chain_id = chains.get(network)
|
| 1044 |
if not chain_id:
|
|
|
|
| 1045 |
return []
|
| 1046 |
|
| 1047 |
all_transfers = []
|
|
|
|
| 1063 |
if response.status_code == 200:
|
| 1064 |
result = response.json().get('result', [])
|
| 1065 |
all_transfers.extend(result)
|
|
|
|
| 1066 |
elif response.status_code == 401:
|
|
|
|
| 1067 |
return []
|
| 1068 |
elif response.status_code == 404:
|
|
|
|
| 1069 |
return []
|
| 1070 |
else:
|
|
|
|
| 1071 |
return []
|
| 1072 |
|
| 1073 |
except Exception as chain_error:
|
|
|
|
| 1074 |
return []
|
| 1075 |
|
| 1076 |
return all_transfers
|
| 1077 |
|
| 1078 |
except Exception as e:
|
|
|
|
| 1079 |
return []
|
| 1080 |
|
| 1081 |
async def _enrich_api_data_with_timing(self, api_data):
|
|
|
|
| 1115 |
enriched_data.append(enriched_transfer)
|
| 1116 |
|
| 1117 |
except Exception as e:
|
|
|
|
| 1118 |
continue
|
| 1119 |
|
| 1120 |
return enriched_data
|
|
|
|
| 1135 |
|
| 1136 |
for transfer in enriched_data:
|
| 1137 |
value = float(transfer.get('value', 0))
|
| 1138 |
+
if value > 1e15:
|
| 1139 |
value = value / 1e18
|
| 1140 |
volumes.append(value)
|
| 1141 |
|
| 1142 |
+
if value > 10000:
|
| 1143 |
large_transfers.append(transfer)
|
| 1144 |
|
| 1145 |
total_volume = sum(volumes)
|
|
|
|
| 1178 |
}
|
| 1179 |
|
| 1180 |
except Exception as e:
|
|
|
|
| 1181 |
return {
|
| 1182 |
'data_available': False,
|
| 1183 |
'description': f'غير متوفر - خطأ في تحليل البيانات',
|
|
|
|
| 1194 |
if self.data_manager:
|
| 1195 |
native_price = await self.data_manager.get_native_coin_price(network)
|
| 1196 |
if native_price:
|
|
|
|
| 1197 |
return {
|
| 1198 |
'data_available': True,
|
| 1199 |
'description': f'تم اكتشاف {symbol} على شبكة {network}',
|
|
|
|
| 1202 |
'source': 'network_scan'
|
| 1203 |
}
|
| 1204 |
except Exception as e:
|
|
|
|
| 1205 |
continue
|
| 1206 |
|
|
|
|
| 1207 |
return {
|
| 1208 |
'data_available': False,
|
| 1209 |
'description': f'غير متوفر - لم يتم العثور على {symbol} على أي شبكة',
|
|
|
|
| 1300 |
return signal
|
| 1301 |
|
| 1302 |
except Exception as e:
|
|
|
|
| 1303 |
return None
|
| 1304 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1305 |
async def cleanup(self):
|
| 1306 |
await self.http_client.aclose()
|
|
|
|
| 1307 |
|
| 1308 |
print("✅ EnhancedWhaleMonitor loaded - Real-time whale activity monitoring ready")
|