Riy777 commited on
Commit
c6f72fe
·
1 Parent(s): ea38153

Update data_manager.py

Browse files
Files changed (1) hide show
  1. data_manager.py +362 -693
data_manager.py CHANGED
@@ -1,4 +1,4 @@
1
- # data_manager.py - الإصدار المحسن مع الإعلان الصريح عن عدم توفر البيانات
2
  import os, asyncio, httpx, json, traceback, backoff, re, time
3
  from datetime import datetime, timedelta
4
  from functools import wraps
@@ -8,24 +8,41 @@ import pandas as pd
8
  import numpy as np
9
  from state import MARKET_STATE_OK
10
 
11
- # --- 🐋 نظام تتبع الحيتان المحسن مع مصادر RPC متعددة ---
12
  class EnhancedWhaleMonitor:
13
  def __init__(self, contracts_db=None):
14
  self.http_client = httpx.AsyncClient(timeout=15.0, limits=httpx.Limits(max_connections=50, max_keepalive_connections=10))
15
 
 
16
  self.moralis_key = os.getenv("MORALIS_KEY")
17
  self.etherscan_key = os.getenv("ETHERSCAN_KEY")
 
18
 
19
  self.whale_threshold_usd = 100000
20
  self.contracts_db = contracts_db or {}
21
 
22
- # 🔄 مصادر RPC متعددة مع أوقات استجابة مختلفة
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  self.rpc_endpoints = {
24
  'ethereum': [
25
- 'https://eth.llamarpc.com',
26
  'https://rpc.ankr.com/eth',
27
  'https://cloudflare-eth.com',
28
- 'https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161'
29
  ],
30
  'bsc': [
31
  'https://bsc-dataseed.binance.org/',
@@ -34,7 +51,8 @@ class EnhancedWhaleMonitor:
34
  ],
35
  'polygon': [
36
  'https://polygon-rpc.com/',
37
- 'https://rpc-mainnet.maticvigil.com'
 
38
  ],
39
  'arbitrum': [
40
  'https://arb1.arbitrum.io/rpc',
@@ -46,31 +64,113 @@ class EnhancedWhaleMonitor:
46
  ]
47
  }
48
 
 
 
 
 
 
 
 
 
49
  self.current_rpc_index = {network: 0 for network in self.rpc_endpoints.keys()}
50
  self.rpc_failures = {network: 0 for network in self.rpc_endpoints.keys()}
51
 
52
  self.price_cache = {}
53
  self.last_scan_time = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
  def _get_next_rpc_endpoint(self, network):
56
- """الحصول على عنوان RPC التالي مع تدوير المصادر"""
57
  if network not in self.rpc_endpoints:
58
  return None
59
 
60
  endpoints = self.rpc_endpoints[network]
61
  if not endpoints:
62
  return None
63
-
 
64
  index = self.current_rpc_index[network]
65
  endpoint = endpoints[index]
66
-
67
- # الانتقال إلى المصدر التالي للطلب القادم
68
  self.current_rpc_index[network] = (index + 1) % len(endpoints)
69
 
70
  return endpoint
71
 
72
  async def _get_native_coin_price(self, network):
73
- """جلب الأسعار من KuCoin مع CoinGecko كاحتياطي"""
74
  now = time.time()
75
  cache_key = f"{network}_price"
76
 
@@ -78,20 +178,12 @@ class EnhancedWhaleMonitor:
78
  if cache_key in self.price_cache and (now - self.price_cache[cache_key]['timestamp']) < 300:
79
  return self.price_cache[cache_key]['price']
80
 
81
- coin_map = {
82
- 'ethereum': 'ETH',
83
- 'bsc': 'BNB',
84
- 'polygon': 'MATIC',
85
- 'arbitrum': 'ETH', # Arbitrum يستخدم ETH للرسوم
86
- 'avalanche': 'AVAX'
87
- }
88
-
89
- symbol = coin_map.get(network)
90
  if not symbol:
91
- return None
92
 
93
  try:
94
- # 🔄 المحاولة الأولى: KuCoin
95
  price = await self._get_price_from_kucoin(symbol)
96
  if price and price > 0:
97
  self.price_cache[cache_key] = {'price': price, 'timestamp': now, 'source': 'kucoin'}
@@ -103,12 +195,6 @@ class EnhancedWhaleMonitor:
103
  self.price_cache[cache_key] = {'price': price, 'timestamp': now, 'source': 'coingecko'}
104
  return price
105
 
106
- # 🔄 استخدام القيمة المخزنة مؤقتاً إذا فشل كل شيء
107
- cached_price = self.price_cache.get(cache_key, {}).get('price')
108
- if cached_price and cached_price > 0:
109
- print(f"⚠️ استخدام السعر المخزن مؤقتاً لـ {network}: ${cached_price}")
110
- return cached_price
111
-
112
  return None
113
 
114
  except Exception as e:
@@ -116,18 +202,29 @@ class EnhancedWhaleMonitor:
116
  return None
117
 
118
  async def _get_price_from_kucoin(self, symbol):
119
- """جلب السعر من KuCoin"""
120
  try:
121
- # استخدام CCxt للوصول إلى KuCoin
122
- exchange = ccxt.kucoin()
123
- ticker = await exchange.fetch_ticker(f"{symbol}/USDT")
124
- await exchange.close()
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
- price = ticker.get('last')
127
- if price and price > 0:
128
- print(f"✅ سعر {symbol} من KuCoin: ${price:.2f}")
129
- return float(price)
130
  return None
 
131
  except Exception as e:
132
  print(f"⚠️ فشل جلب السعر من KuCoin لـ {symbol}: {e}")
133
  return None
@@ -138,7 +235,7 @@ class EnhancedWhaleMonitor:
138
  'ethereum': 'ethereum',
139
  'bsc': 'binancecoin',
140
  'polygon': 'matic-network',
141
- 'arbitrum': 'ethereum', # Arbitrum يستخدم ETH
142
  'avalanche': 'avalanche-2'
143
  }
144
 
@@ -147,7 +244,7 @@ class EnhancedWhaleMonitor:
147
  return None
148
 
149
  try:
150
- await asyncio.sleep(0.5) # تأخير بسيط لتجنب rate limits
151
  url = f"https://api.coingecko.com/api/v3/simple/price?ids={coin_id}&vs_currencies=usd"
152
  response = await self.http_client.get(url)
153
  response.raise_for_status()
@@ -161,8 +258,8 @@ class EnhancedWhaleMonitor:
161
  return None
162
 
163
  async def _call_rpc_async(self, network, method, params=[]):
164
- """اتصال RPC غير متزامن مع إعادة المحاولة"""
165
- max_retries = 3
166
 
167
  for attempt in range(max_retries):
168
  endpoint = self._get_next_rpc_endpoint(network)
@@ -171,34 +268,55 @@ class EnhancedWhaleMonitor:
171
  return None
172
 
173
  try:
 
 
 
 
 
 
 
 
 
174
  payload = {"jsonrpc": "2.0", "method": method, "params": params, "id": 1}
175
 
176
- # زيادة المهلة للطلبات الثقيلة
177
- timeout = 30.0 if method == 'eth_getBlockByNumber' else 15.0
178
 
179
  async with httpx.AsyncClient(timeout=timeout) as client:
180
  response = await client.post(endpoint, json=payload)
 
 
 
 
 
 
 
 
 
 
181
  response.raise_for_status()
182
  result = response.json().get('result')
183
 
184
- # إعادة تعيين عداد الفشل عند النجاح
185
  self.rpc_failures[network] = 0
186
  return result
187
 
188
  except httpx.HTTPStatusError as e:
189
- if e.response.status_code == 429: # Too Many Requests
190
  print(f"⚠️ Rate limit على {endpoint} لـ {network}. المحاولة {attempt + 1}/{max_retries}")
191
  self.rpc_failures[network] += 1
192
- await asyncio.sleep(2 * (attempt + 1)) # زيادة التأخير
 
 
 
 
193
  continue
194
  else:
195
  print(f"⚠️ خطأ HTTP {e.response.status_code} في {endpoint} لـ {network}: {e}")
 
196
 
197
  except Exception as e:
198
  print(f"⚠️ فشل اتصال RPC لـ {network} على {endpoint}: {e}")
199
  self.rpc_failures[network] += 1
200
 
201
- # الانتظار قبل إعادة المحاولة
202
  if attempt < max_retries - 1:
203
  await asyncio.sleep(1 * (attempt + 1))
204
 
@@ -206,7 +324,7 @@ class EnhancedWhaleMonitor:
206
  return None
207
 
208
  async def _scan_single_evm_network(self, network):
209
- """مسح شبكة EVM واحدة مع تحسين التعامل مع الأخطاء"""
210
  whale_alerts = []
211
  try:
212
  price_usd = await self._get_native_coin_price(network)
@@ -220,8 +338,9 @@ class EnhancedWhaleMonitor:
220
 
221
  latest_block = int(latest_block_hex, 16)
222
 
223
- # 🔄 تقليل عدد الكتل من 50 إلى 20 للأداء
224
- blocks_to_scan = 20
 
225
  for block_offset in range(blocks_to_scan):
226
  block_number = latest_block - block_offset
227
  if block_number < 0:
@@ -231,13 +350,13 @@ class EnhancedWhaleMonitor:
231
  if not block_data or 'transactions' not in block_data:
232
  continue
233
 
234
- # 🕒 استخراج وقت الكتلة
 
235
  block_timestamp_hex = block_data.get('timestamp', '0x0')
236
  block_timestamp = int(block_timestamp_hex, 16)
237
  block_time = datetime.fromtimestamp(block_timestamp)
238
  time_ago = datetime.now() - block_time
239
 
240
- # 🔍 تحليل المعاملات الكبيرة فقط
241
  for tx in block_data.get('transactions', []):
242
  value_wei = int(tx.get('value', '0x0'), 16)
243
  if value_wei > 0:
@@ -257,9 +376,10 @@ class EnhancedWhaleMonitor:
257
  'transaction_type': 'native_transfer'
258
  })
259
 
260
- # إضافة تأخير بسيط بين الكتل
261
- if block_offset % 5 == 0:
262
- await asyncio.sleep(0.1)
 
263
 
264
  except Exception as e:
265
  print(f"⚠️ خطأ في مسح شبكة {network}: {e}")
@@ -267,23 +387,25 @@ class EnhancedWhaleMonitor:
267
  return whale_alerts
268
 
269
  async def get_general_whale_activity(self):
270
- """الوظيفة الرئيسية لمراقبة الحيتان مع تحسين المرونة"""
271
  print("🌊 بدء مراقبة الحيتان عبر الشبكات المتعددة...")
272
 
273
  try:
274
- # مسح الشبكات بالتوازي مع حدود
275
  tasks = []
276
- for network in list(self.rpc_endpoints.keys())[:3]: # تقليل إلى 3 شبكات للأداء
 
277
  tasks.append(self._scan_single_evm_network(network))
278
 
279
  results = await asyncio.gather(*tasks, return_exceptions=True)
280
 
281
  all_alerts = []
 
 
282
  for res in results:
283
  if isinstance(res, list):
284
  all_alerts.extend(res)
 
285
 
286
- # 🕒 ترتيب التنبيهات من الأحدث إلى الأقدم
287
  all_alerts.sort(key=lambda x: x['timestamp'], reverse=True)
288
 
289
  total_volume = sum(alert['value_usd'] for alert in all_alerts)
@@ -297,17 +419,17 @@ class EnhancedWhaleMonitor:
297
  'sentiment': 'UNKNOWN',
298
  'total_volume_usd': 0,
299
  'transaction_count': 0,
300
- 'data_quality': 'HIGH'
 
301
  }
302
 
303
- # 🕒 أحدث معاملة
304
  latest_alert = all_alerts[0] if all_alerts else None
305
  latest_time_info = f"آخر نشاط منذ {latest_alert['minutes_ago']:.1f} دقيقة" if latest_alert else ""
306
 
307
  sentiment = 'BULLISH' if total_volume > 5_000_000 else 'SLIGHTLY_BULLISH' if total_volume > 1_000_000 else 'NEUTRAL'
308
  critical = total_volume > 10_000_000 or any(tx['value_usd'] > 5_000_000 for tx in all_alerts)
309
 
310
- description = f"تم اكتشاف {alert_count} معاملة حوت بإجمالي ${total_volume:,.0f} عبر {len([r for r in results if isinstance(r, list) and r])} شبكات. {latest_time_info}"
311
  print(f"✅ {description}")
312
 
313
  return {
@@ -325,7 +447,7 @@ class EnhancedWhaleMonitor:
325
  'average_minutes': sum(alert['minutes_ago'] for alert in all_alerts) / len(all_alerts) if all_alerts else 0
326
  },
327
  'data_quality': 'HIGH',
328
- 'networks_scanned': len(tasks)
329
  }
330
 
331
  except Exception as e:
@@ -342,7 +464,7 @@ class EnhancedWhaleMonitor:
342
  }
343
 
344
  async def get_symbol_specific_whale_data(self, symbol, contract_address=None):
345
- """جلب بيانات الحيتان الخاصة بعملة محددة مع التعامل مع الأخطاء"""
346
  try:
347
  base_symbol = symbol.split("/")[0] if '/' in symbol else symbol
348
 
@@ -372,71 +494,6 @@ class EnhancedWhaleMonitor:
372
  'source': 'error'
373
  }
374
 
375
- async def _find_contract_address(self, base_symbol):
376
- """البحث عن عنوان العقد من مصادر متعددة"""
377
- contracts = self.contracts_db.get(base_symbol.upper(), {})
378
- if contracts:
379
- for network in ['ethereum', 'bsc', 'polygon', 'arbitrum']:
380
- if network in contracts:
381
- print(f"✅ وجد عقد {base_symbol} في {network}: {contracts[network][:10]}...")
382
- return contracts[network]
383
-
384
- print(f"🔍 البحث عن عقد {base_symbol} في CoinGecko...")
385
- coin_id = await self._find_coin_id_by_symbol(base_symbol)
386
- if coin_id:
387
- contract_address = await self._get_contract_from_coingecko(coin_id)
388
- if contract_address:
389
- print(f"✅ وجد عقد {base_symbol} في CoinGecko: {contract_address[:10]}...")
390
- return contract_address
391
-
392
- print(f"⚠️ لم يتم العثور على عقد لـ {base_symbol}")
393
- return None
394
-
395
- async def _find_coin_id_by_symbol(self, symbol):
396
- """البحث عن معرف العملة في CoinGecko"""
397
- try:
398
- url = f"https://api.coingecko.com/api/v3/coins/list"
399
- async with httpx.AsyncClient() as client:
400
- response = await client.get(url, timeout=10)
401
- response.raise_for_status()
402
- coins = response.json()
403
-
404
- for coin in coins:
405
- if coin['symbol'].upper() == symbol.upper():
406
- return coin['id']
407
-
408
- for coin in coins:
409
- if symbol.upper() in coin['symbol'].upper() or symbol.upper() in coin['name'].upper():
410
- return coin['id']
411
-
412
- except Exception as e:
413
- print(f"⚠️ فشل البحث عن معرف العملة لـ {symbol}: {e}")
414
-
415
- return None
416
-
417
- async def _get_contract_from_coingecko(self, coin_id):
418
- """جلب عنوان العقد من CoinGecko"""
419
- try:
420
- url = f"https://api.coingecko.com/api/v3/coins/{coin_id}"
421
- async with httpx.AsyncClient() as client:
422
- response = await client.get(url, timeout=10)
423
- response.raise_for_status()
424
- data = response.json()
425
-
426
- platforms = data.get('platforms', {})
427
- for platform, address in platforms.items():
428
- if address and address.strip():
429
- return address
430
-
431
- contract_address = data.get('contract_address')
432
- if contract_address:
433
- return contract_address
434
-
435
- except Exception as e:
436
- print(f"⚠️ فشل جلب العقد من CoinGecko لـ {coin_id}: {e}")
437
-
438
- return None
439
-
440
  async def _get_combined_api_data(self, contract_address):
441
  """جلب البيانات المجمعة من مصادر API"""
442
  tasks = []
@@ -444,7 +501,7 @@ class EnhancedWhaleMonitor:
444
  if self.moralis_key:
445
  tasks.append(self._get_moralis_token_data(contract_address))
446
  if self.etherscan_key:
447
- tasks.append(self._get_etherscan_token_data(contract_address))
448
 
449
  if not tasks:
450
  return []
@@ -458,244 +515,115 @@ class EnhancedWhaleMonitor:
458
 
459
  return all_transfers
460
 
461
- async def _enrich_api_data_with_timing(self, api_data):
462
- """إثراء بيانات API بمعلومات التوقيت"""
463
- enriched_data = []
464
- current_time = datetime.now()
465
-
466
- for transfer in api_data:
467
- try:
468
- if 'block_timestamp' in transfer:
469
- timestamp = transfer['block_timestamp']
470
- elif 'timeStamp' in transfer:
471
- timestamp = int(transfer['timeStamp'])
472
- else:
473
- timestamp = int(current_time.timestamp()) - np.random.randint(300, 1800)
474
-
475
- transfer_time = datetime.fromtimestamp(timestamp)
476
- minutes_ago = (current_time - transfer_time).total_seconds() / 60
477
-
478
- transfer['whale_timing'] = {
479
- 'timestamp': timestamp,
480
- 'human_time': transfer_time.isoformat(),
481
- 'minutes_ago': minutes_ago,
482
- 'recency_score': max(0, 1 - (minutes_ago / 60))
483
- }
484
-
485
- enriched_data.append(transfer)
486
-
487
- except Exception as e:
488
- print(f"⚠️ خطأ في إثراء بيانات التوقيت: {e}")
489
- continue
490
-
491
- return enriched_data
492
-
493
- async def _scan_networks_for_symbol(self, symbol, base_symbol):
494
- """مسح الشبكات المباشرة للبحث عن نشاط الحيتان للعملة"""
495
- print(f"🌊 مسح الشبكات المباشرة لـ {symbol}...")
496
-
497
- whale_alerts = []
498
- tasks = []
499
-
500
- for network in ['ethereum', 'bsc', 'polygon', 'arbitrum', 'avalanche']:
501
- if network in self.rpc_endpoints:
502
- tasks.append(self._scan_network_for_token_transfers(network, base_symbol))
503
-
504
- results = await asyncio.gather(*tasks, return_exceptions=True)
505
-
506
- for res in results:
507
- if isinstance(res, list):
508
- whale_alerts.extend(res)
509
-
510
- whale_alerts.sort(key=lambda x: x['timestamp'], reverse=True)
511
-
512
- return self._analyze_network_scan_results(whale_alerts, symbol)
513
-
514
- async def _scan_network_for_token_transfers(self, network, base_symbol):
515
- """مسح شبكة محددة لتحويلات الرمز المميز مع الوقت الدقيق"""
516
  try:
517
- print(f"🔍 مسح {network} لـ {base_symbol}...")
 
518
 
519
- latest_block_hex = await self._call_rpc_async(network, 'eth_blockNumber')
520
- if not latest_block_hex:
 
521
  return []
522
 
523
- latest_block = int(latest_block_hex, 16)
524
- whale_alerts = []
525
-
526
- for block_offset in range(20):
527
- block_number = latest_block - block_offset
528
- if block_number < 0:
529
- break
530
-
531
- block_data = await self._call_rpc_async(network, 'eth_getBlockByNumber', [hex(block_number), True])
532
- if not block_data or 'transactions' not in block_data:
533
- continue
534
-
535
- block_timestamp_hex = block_data.get('timestamp', '0x0')
536
- block_timestamp = int(block_timestamp_hex, 16)
537
- block_time = datetime.fromtimestamp(block_timestamp)
538
- time_ago = (datetime.now() - block_time).total_seconds() / 60
539
-
540
- for tx in block_data.get('transactions', []):
541
- if await self._check_transaction_relevance(tx, base_symbol, network):
542
- value_wei = int(tx.get('value', '0x0'), 16)
543
- value_eth = value_wei / 1e18
544
-
545
- price_usd = await self._get_native_coin_price(network)
546
- if price_usd is None:
547
- continue
548
-
549
- value_usd = value_eth * price_usd
550
-
551
- if value_usd >= self.whale_threshold_usd:
552
- whale_alerts.append({
553
- 'network': network,
554
- 'value_usd': value_usd,
555
- 'from': tx.get('from', '')[:10] + '...',
556
- 'to': tx.get('to', '')[:10] + '...',
557
- 'hash': tx.get('hash', '')[:10] + '...',
558
- 'block_number': block_number,
559
- 'timestamp': block_timestamp,
560
- 'human_time': block_time.isoformat(),
561
- 'minutes_ago': time_ago,
562
- 'transaction_type': 'direct_transfer',
563
- 'recency_score': max(0, 1 - (time_ago / 60))
564
- })
565
-
566
- return whale_alerts
567
-
568
- except Exception as e:
569
- print(f"⚠️ فشل مسح {network} لـ {base_symbol}: {e}")
570
- return []
571
-
572
- async def _check_transaction_relevance(self, transaction, base_symbol, network):
573
- """التحقق من صلة المعاملة بالرمز المطلوب"""
574
- try:
575
- base_symbol_lower = base_symbol.lower()
576
 
577
- input_data = transaction.get('input', '').lower()
578
- if base_symbol_lower in input_data:
579
- return True
580
 
581
- from_addr = transaction.get('from', '').lower()
582
- to_addr = transaction.get('to', '').lower()
583
 
584
- if base_symbol_lower in from_addr or base_symbol_lower in to_addr:
585
- return True
586
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
587
  except Exception as e:
588
- print(f"⚠️ خطأ في فحص صلة المعاملة: {e}")
589
-
590
- return False
591
-
592
- def _analyze_network_scan_results(self, alerts, symbol):
593
- """تحليل نتائج المسح الشبكي مع التحليل الزمني"""
594
- if not alerts:
595
- return {
596
- 'data_available': False,
597
- 'description': f'لم يتم العثور على نشاط حيتان لـ {symbol} في آخر 20 كتلة',
598
- 'total_volume': 0,
599
- 'transfer_count': 0
600
- }
601
-
602
- total_volume = sum(alert['value_usd'] for alert in alerts)
603
- transfer_count = len(alerts)
604
-
605
- latest_alert = alerts[0] if alerts else None
606
- oldest_alert = alerts[-1] if alerts else None
607
-
608
- time_analysis = {
609
- 'latest_minutes_ago': latest_alert['minutes_ago'] if latest_alert else 0,
610
- 'oldest_minutes_ago': oldest_alert['minutes_ago'] if oldest_alert else 0,
611
- 'average_minutes_ago': sum(alert['minutes_ago'] for alert in alerts) / len(alerts) if alerts else 0
612
- }
613
-
614
- avg_recency = sum(alert.get('recency_score', 0) for alert in alerts) / len(alerts) if alerts else 0
615
-
616
- sentiment = 'BULLISH' if total_volume > 500000 else 'SLIGHTLY_BULLISH' if total_volume > 100000 else 'NEUTRAL'
617
-
618
- if time_analysis['latest_minutes_ago'] < 5:
619
- sentiment = 'STRONGLY_BULLISH'
620
- description = f'🚨 {symbol}: نشاط حيتان حديث جداً ({time_analysis["latest_minutes_ago"]:.1f} دقيقة) - {transfer_count} تحويل بإجمالي ${total_volume:,.0f}'
621
- else:
622
- description = f'{symbol}: {transfer_count} تحويل حيتان، آخرها منذ {time_analysis["latest_minutes_ago"]:.1f} دقيقة - إجمالي ${total_volume:,.0f}'
623
-
624
- return {
625
- 'sentiment': sentiment,
626
- 'description': description,
627
- 'total_volume': total_volume,
628
- 'transfer_count': transfer_count,
629
- 'data_available': True,
630
- 'source': 'direct_network_scan',
631
- 'time_analysis': time_analysis,
632
- 'recency_score': avg_recency,
633
- 'latest_activity': latest_alert['human_time'] if latest_alert else None,
634
- 'recent_alerts': alerts[:10]
635
- }
636
-
637
- def _analyze_symbol_specific_data(self, transfers, symbol):
638
- """تحليل البيانات الخاصة بالرمز مع التوقيت"""
639
- if not transfers:
640
- return {
641
- 'data_available': False,
642
- 'description': f'لا يوجد نشاط حيتان حديث لـ {symbol}',
643
- 'total_volume': 0,
644
- 'transfer_count': 0
645
- }
646
-
647
- total_volume = sum(int(tx.get('value', 0)) for tx in transfers)
648
- decimals = 18
649
- try:
650
- if transfers and 'tokenDecimal' in transfers[0]:
651
- decimals = int(transfers[0]['tokenDecimal'])
652
- except: pass
653
-
654
- total_volume_normalized = total_volume / (10 ** decimals)
655
-
656
- timing_info = []
657
- for tx in transfers[:5]:
658
- if 'whale_timing' in tx:
659
- timing_info.append(f"{tx['whale_timing']['minutes_ago']:.1f}د")
660
-
661
- timing_summary = ", ".join(timing_info) if timing_info else "توقيت غير معروف"
662
-
663
- sentiment = 'BULLISH' if total_volume_normalized > 1000000 else 'SLIGHTLY_BULLISH'
664
-
665
- return {
666
- 'sentiment': sentiment,
667
- 'description': f'{symbol}: {len(transfers)} تحويل حيتان ({timing_summary}) - {total_volume_normalized:,.0f} عملة',
668
- 'total_volume': total_volume_normalized,
669
- 'transfer_count': len(transfers),
670
- 'data_available': True,
671
- 'source': 'onchain_apis',
672
- 'recent_transfers': transfers[:5]
673
- }
674
 
675
  async def _get_moralis_token_data(self, contract_address):
676
- if not self.moralis_key: return []
 
 
 
677
  try:
678
  response = await self.http_client.get(
679
  f"https://deep-index.moralis.io/api/v2/erc20/{contract_address}/transfers",
680
  headers={"X-API-Key": self.moralis_key},
681
- params={"chain": "eth", "limit": 20}
682
  )
683
- return response.json().get('result', []) if response.status_code == 200 else []
 
 
 
 
 
 
 
 
684
  except Exception as e:
685
- print(f"⚠️ Moralis API error: {e}"); return []
 
686
 
687
- async def _get_etherscan_token_data(self, contract_address):
688
- if not self.etherscan_key: return []
689
- try:
690
- params = {
691
- "module": "account", "action": "tokentx", "contractaddress": contract_address,
692
- "page": 1, "offset": 20, "sort": "desc", "apikey": self.etherscan_key
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
693
  }
694
- response = await self.http_client.get("https://api.etherscan.io/api", params=params)
695
- data = response.json()
696
- return data.get('result', []) if response.status_code == 200 and data.get('status') == '1' else []
697
- except Exception as e:
698
- print(f"⚠️ Etherscan API error: {e}"); return []
699
 
700
  # إنشاء نسخة عالمية
701
  whale_monitor_global = EnhancedWhaleMonitor()
@@ -703,14 +631,20 @@ whale_monitor_global = EnhancedWhaleMonitor()
703
  class DataManager:
704
  def __init__(self, contracts_db):
705
  self.contracts_db = contracts_db or {}
706
- self.exchange = ccxt.kucoin({
707
- 'apiKey': os.getenv('KUCOIN_API_KEY', ''),
708
- 'secret': os.getenv('KUCOIN_SECRET', ''),
709
- 'password': os.getenv('KUCOIN_PASSWORD', ''),
710
- 'sandbox': False,
711
- 'enableRateLimit': True
712
- })
713
- self.exchange.rateLimit = 800
 
 
 
 
 
 
714
  self._whale_data_cache = {}
715
  self.http_client = None
716
  self.fetch_stats = {'successful_fetches': 0, 'failed_fetches': 0, 'rate_limit_hits': 0}
@@ -719,12 +653,36 @@ class DataManager:
719
 
720
  async def initialize(self):
721
  self.http_client = httpx.AsyncClient(timeout=20.0)
722
- print("✅ DataManager initialized - Enhanced RPC with KuCoin Pricing")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
723
 
724
  async def close(self):
725
  if self.http_client:
726
  await self.http_client.aclose()
727
- await self.exchange.close()
 
728
 
729
  async def get_sentiment_safe_async(self):
730
  """جلب بيانات المشاعر مع مصادر احتياطية"""
@@ -752,15 +710,14 @@ class DataManager:
752
  if attempt < max_retries - 1:
753
  await asyncio.sleep(1)
754
 
755
- print("🚨 فشل جميع محاولات جلب بيانات المشاعر")
756
  return None
757
 
758
  async def get_market_context_async(self):
759
- """جلب سياق السوق مع الإعلان الصريح عن البيانات غير المتوفرة"""
760
  max_retries = 2
761
  for attempt in range(max_retries):
762
  try:
763
- print(f"📊 جلب سياق السوق - المحاولة {attempt + 1}/{max_retries}...")
764
 
765
  sentiment_task = asyncio.wait_for(self.get_sentiment_safe_async(), timeout=10)
766
  price_task = asyncio.wait_for(self._get_prices_with_fallback(), timeout=15)
@@ -773,20 +730,9 @@ class DataManager:
773
  price_data = results[1] if not isinstance(results[1], Exception) else {}
774
  general_whale_activity = results[2] if not isinstance(results[2], Exception) else None
775
 
776
- data_quality_issues = []
777
-
778
- if sentiment_data is None:
779
- data_quality_issues.append("بيانات المشاعر غير متوفرة")
780
-
781
  bitcoin_price = price_data.get('bitcoin')
782
  ethereum_price = price_data.get('ethereum')
783
 
784
- if bitcoin_price is None or ethereum_price is None:
785
- data_quality_issues.append("بيانات الأسعار غير متوفرة")
786
-
787
- if general_whale_activity is None or not general_whale_activity.get('data_available'):
788
- data_quality_issues.append("بيانات الحيتان غير متوفرة")
789
-
790
  if bitcoin_price is None or ethereum_price is None:
791
  if attempt < max_retries - 1:
792
  await asyncio.sleep(2)
@@ -796,20 +742,6 @@ class DataManager:
796
 
797
  market_trend = self._determine_market_trend(bitcoin_price, sentiment_data, general_whale_activity)
798
 
799
- whale_timing_info = {}
800
- if general_whale_activity and general_whale_activity.get('data_available'):
801
- whale_timing_info = {
802
- 'latest_activity': general_whale_activity.get('latest_activity'),
803
- 'time_analysis': general_whale_activity.get('time_analysis', {}),
804
- 'recent_alerts_count': len(general_whale_activity.get('recent_alerts', []))
805
- }
806
-
807
- data_quality = 'HIGH'
808
- if len(data_quality_issues) > 1:
809
- data_quality = 'MEDIUM'
810
- elif len(data_quality_issues) > 2:
811
- data_quality = 'LOW'
812
-
813
  market_context = {
814
  'timestamp': datetime.now().isoformat(),
815
  'bitcoin_price_usd': bitcoin_price,
@@ -822,7 +754,6 @@ class DataManager:
822
  'critical_alert': False,
823
  'sentiment': 'UNKNOWN'
824
  },
825
- 'whale_timing_info': whale_timing_info,
826
  'market_trend': market_trend,
827
  'btc_sentiment': self._get_btc_sentiment(bitcoin_price),
828
  'data_sources': {
@@ -830,21 +761,14 @@ class DataManager:
830
  'sentiment': sentiment_data is not None,
831
  'general_whale_data': general_whale_activity.get('data_available', False) if general_whale_activity else False
832
  },
833
- 'data_quality': data_quality,
834
- 'retry_attempt': attempt + 1,
835
- 'missing_data': data_quality_issues
836
  }
837
 
838
- print(f"📊 سياق السوق: BTC=${bitcoin_price:,.0f}, F&G={'متوفر' if sentiment_data else 'غير متوفر'}, جودة البيانات: {data_quality}")
839
- if data_quality_issues:
840
- print(f"⚠️ بيانات مفقودة: {', '.join(data_quality_issues)}")
841
 
842
  return market_context
843
 
844
- except asyncio.TimeoutError:
845
- print(f"⏰ انتهت المهلة في محاولة {attempt + 1} لجلب سياق السوق")
846
- if attempt < max_retries - 1:
847
- await asyncio.sleep(3)
848
  except Exception as e:
849
  print(f"❌ فشل محاولة {attempt + 1}/{max_retries} لجلب سياق السوق: {e}")
850
  if attempt < max_retries - 1:
@@ -853,7 +777,7 @@ class DataManager:
853
  return self._get_minimal_market_context()
854
 
855
  def _get_btc_sentiment(self, bitcoin_price):
856
- """تحديد اتجاه البيتكوين مع التعامل مع القيم غير المتوفرة"""
857
  if bitcoin_price is None:
858
  return 'UNKNOWN'
859
  elif bitcoin_price > 60000:
@@ -864,9 +788,9 @@ class DataManager:
864
  return 'NEUTRAL'
865
 
866
  async def _get_prices_with_fallback(self):
867
- """جلب الأسعار مع KuCoin كأولوية وCoinGecko كاحتياطي"""
868
  try:
869
- prices = await self._get_prices_from_kucoin()
870
  if prices.get('bitcoin') and prices.get('ethereum'):
871
  return prices
872
 
@@ -880,22 +804,35 @@ class DataManager:
880
  print(f"❌ فشل جلب الأسعار: {e}")
881
  return {'bitcoin': None, 'ethereum': None}
882
 
883
- async def _get_prices_from_kucoin(self):
884
- """جلب الأسعار من KuCoin"""
 
 
 
885
  try:
886
- btc_ticker = await self.exchange.fetch_ticker('BTC/USDT')
887
- eth_ticker = await self.exchange.fetch_ticker('ETH/USDT')
 
 
 
 
 
 
 
 
 
888
 
889
- btc_price = float(btc_ticker.get('last', 0)) if btc_ticker.get('last') else None
890
- eth_price = float(eth_ticker.get('last', 0)) if eth_ticker.get('last') else None
 
 
 
 
 
 
 
891
 
892
- if btc_price and eth_price:
893
- self.price_cache['bitcoin'] = btc_price
894
- self.price_cache['ethereum'] = eth_price
895
- print(f"✅ الأسعار من KuCoin: BTC=${btc_price:.0f}, ETH=${eth_price:.0f}")
896
- return {'bitcoin': btc_price, 'ethereum': eth_price}
897
-
898
- return {'bitcoin': None, 'ethereum': None}
899
 
900
  except Exception as e:
901
  print(f"⚠️ فشل جلب الأسعار من KuCoin: {e}")
@@ -927,7 +864,7 @@ class DataManager:
927
  return {'bitcoin': None, 'ethereum': None}
928
 
929
  def _get_minimal_market_context(self):
930
- """إرجاع سياق سوق أساسي مع الإعلان الصريح عن عدم توفر البيانات"""
931
  return {
932
  'timestamp': datetime.now().isoformat(),
933
  'data_available': False,
@@ -950,7 +887,7 @@ class DataManager:
950
  }
951
 
952
  def _determine_market_trend(self, bitcoin_price, sentiment_data, whale_activity):
953
- """تحديد اتجاه السوق مع التعامل الصحيح مع البيانات غير المتوفرة"""
954
  try:
955
  if bitcoin_price is None:
956
  return "UNKNOWN"
@@ -999,294 +936,26 @@ class DataManager:
999
  print(f"⚠️ خطأ في تحديد اتجاه السوق: {e}")
1000
  return "UNKNOWN"
1001
 
1002
- @staticmethod
1003
- def adaptive_backoff(func):
1004
- @backoff.on_exception(backoff.expo, (RateLimitExceeded, DDoSProtection, NetworkError, httpx.TimeoutException),
1005
- max_tries=3, max_time=20, on_backoff=lambda details: print(f"⏳ Backoff: Attempt {details['tries']}, waiting {details['wait']:.1f}s"))
1006
- @wraps(func)
1007
- async def wrapper(*args, **kwargs):
1008
- return await func(*args, **kwargs)
1009
- return wrapper
1010
-
1011
- @adaptive_backoff
1012
- async def fetch_ohlcv_with_retry(self, symbol: str, timeframe: str, limit: int = 100):
1013
- """جلب بيانات OHLCV مع إعادة المحاولة"""
1014
- try:
1015
- candles = await self.exchange.fetch_ohlcv(symbol, timeframe=timeframe, limit=limit)
1016
- self.fetch_stats['successful_fetches'] += 1
1017
- return candles
1018
- except RateLimitExceeded:
1019
- self.fetch_stats['rate_limit_hits'] += 1
1020
- print(f"⚠️ Rate limit hit for {symbol} on {timeframe}")
1021
- raise
1022
- except Exception as e:
1023
- self.fetch_stats['failed_fetches'] += 1
1024
- print(f"❌ Failed to fetch {symbol} {timeframe}: {e}")
1025
- raise
1026
-
1027
- async def _scan_for_momentum(self, tickers, top_n=30):
1028
- """مسح الزخم"""
1029
- print("🔍 Running Momentum Scanner...")
1030
- valid_tickers = [t for t in tickers if t.get('change') is not None]
1031
- if not valid_tickers:
1032
- print("⚠️ No valid tickers for momentum analysis")
1033
- return {}
1034
-
1035
- sorted_by_change = sorted(valid_tickers, key=lambda x: x.get('change', 0), reverse=True)
1036
- return {ticker['symbol']: {'momentum'} for ticker in sorted_by_change[:top_n]}
1037
-
1038
- async def _scan_for_breakouts(self, symbols, top_n=30):
1039
- """مسح الانكسارات"""
1040
- print("🧱 Running Breakout Scanner...")
1041
- candidates = {}
1042
- tasks = [self.exchange.fetch_ohlcv(symbol, '1h', limit=48) for symbol in symbols]
1043
- results = await asyncio.gather(*tasks, return_exceptions=True)
1044
-
1045
- for i, result in enumerate(results):
1046
- if isinstance(result, Exception) or not result:
1047
- continue
1048
-
1049
- df = pd.DataFrame(result, columns=['time', 'open', 'high', 'low', 'close', 'volume'])
1050
- if len(df) < 2:
1051
- continue
1052
-
1053
- recent_high = df['high'].iloc[:-1].max()
1054
- current_price = df['close'].iloc[-1]
1055
-
1056
- if current_price > recent_high * 1.02:
1057
- candidates[symbols[i]] = {'breakout'}
1058
- if len(candidates) >= top_n:
1059
- break
1060
-
1061
- print(f"✅ Found {len(candidates)} breakout candidates")
1062
- return candidates
1063
-
1064
- async def _scan_for_volume_spike(self, symbols, top_n=30):
1065
- """مسح ارتفاع الحجم"""
1066
- print("💧 Running Volume Spike Scanner...")
1067
- candidates = {}
1068
- tasks = [self.exchange.fetch_ohlcv(symbol, '1h', limit=24) for symbol in symbols]
1069
- results = await asyncio.gather(*tasks, return_exceptions=True)
1070
-
1071
- for i, result in enumerate(results):
1072
- if isinstance(result, Exception) or not result:
1073
- continue
1074
-
1075
- df = pd.DataFrame(result, columns=['time', 'open', 'high', 'low', 'close', 'volume'])
1076
- df['volume'] = pd.to_numeric(df['volume'])
1077
- if len(df) < 2:
1078
- continue
1079
-
1080
- average_volume = df['volume'].iloc[:-1].mean()
1081
- current_volume = df['volume'].iloc[-1]
1082
-
1083
- if current_volume > average_volume * 3 and current_volume > 10000:
1084
- candidates[symbols[i]] = {'volume_spike'}
1085
- if len(candidates) >= top_n:
1086
- break
1087
-
1088
- print(f"✅ Found {len(candidates)} volume spike candidates")
1089
- return candidates
1090
-
1091
- async def find_high_potential_candidates(self, n=50):
1092
- """إيجاد مرشحين عاليي الإمكانية مع تحسين الأداء"""
1093
- print("🚀 Starting High Potential Candidate Scan...")
1094
- try:
1095
- all_tickers = list((await self.exchange.fetch_tickers()).values())
1096
- if not all_tickers:
1097
- print("❌ Failed to fetch tickers")
1098
- return []
1099
-
1100
- usdt_tickers = [
1101
- t for t in all_tickers
1102
- if '/USDT' in t.get('symbol', '')
1103
- and t.get('quoteVolume', 0) > 50000
1104
- ]
1105
-
1106
- if not usdt_tickers:
1107
- print("❌ No USDT pairs with sufficient volume")
1108
- return []
1109
-
1110
- top_volume_symbols = [
1111
- t['symbol'] for t in sorted(usdt_tickers, key=lambda x: x.get('quoteVolume', 0), reverse=True)[:150]
1112
- ]
1113
-
1114
- print(f"📊 Analyzing {len(top_volume_symbols)} symbols with highest volume")
1115
-
1116
- momentum_task = self._scan_for_momentum(usdt_tickers, top_n=30)
1117
- breakout_task = self._scan_for_breakouts(top_volume_symbols, top_n=30)
1118
- volume_spike_task = self._scan_for_volume_spike(top_volume_symbols, top_n=30)
1119
-
1120
- results = await asyncio.gather(momentum_task, breakout_task, volume_spike_task, return_exceptions=True)
1121
- momentum_candidates, breakout_candidates, volume_spike_candidates = results
1122
-
1123
- combined_candidates = {}
1124
- for candidates_dict in [momentum_candidates, breakout_candidates, volume_spike_candidates]:
1125
- if isinstance(candidates_dict, dict):
1126
- for symbol, reasons in candidates_dict.items():
1127
- combined_candidates.setdefault(symbol, set()).update(reasons)
1128
-
1129
- if not MARKET_STATE_OK:
1130
- print("🚨 Risk filter: Market state is NOT OK. Halting search.")
1131
- return []
1132
-
1133
- final_list = []
1134
- tickers_map = {t['symbol']: t for t in usdt_tickers}
1135
-
1136
- for symbol, reasons in combined_candidates.items():
1137
- ticker_info = tickers_map.get(symbol)
1138
- if not ticker_info:
1139
- continue
1140
-
1141
- change_percent = ticker_info.get('change', 0) or 0
1142
- if change_percent > 300:
1143
- print(f"⚠️ Risk filter: Skipping {symbol} due to over-extension ({change_percent:.2f}%).")
1144
- continue
1145
-
1146
- quote_volume = ticker_info.get('quoteVolume', 0)
1147
- if quote_volume < 100000:
1148
- continue
1149
-
1150
- final_list.append({
1151
- 'symbol': symbol,
1152
- 'reasons': list(reasons),
1153
- 'score': len(reasons),
1154
- 'change_percent': change_percent,
1155
- 'volume': quote_volume
1156
- })
1157
-
1158
- sorted_final_list = sorted(final_list, key=lambda x: x['score'], reverse=True)
1159
- print(f"✅ Scan complete. Found {len(sorted_final_list)} potential candidates.")
1160
-
1161
- return sorted_final_list[:n]
1162
-
1163
- except Exception as e:
1164
- print(f"❌ Failed to find high potential candidates: {e}")
1165
- return []
1166
-
1167
- async def get_whale_data_safe_async(self, symbol):
1168
- """جلب بيانات الحيتان الخاصة برمز معين مع التعامل مع الأخطاء"""
1169
- try:
1170
- base = symbol.split("/")[0]
1171
- contracts = self.contracts_db.get(base, {})
1172
- contract_address = contracts.get("ethereum")
1173
-
1174
- whale_data = await self.whale_monitor.get_symbol_specific_whale_data(symbol, contract_address)
1175
-
1176
- if whale_data.get('data_available'):
1177
- print(f"✅ بيانات على السلسلة لـ {symbol}: {whale_data.get('description')}")
1178
- else:
1179
- print(f"ℹ️ لا توجد بيانات حيتان محددة لـ {symbol}")
1180
-
1181
- return whale_data
1182
-
1183
- except Exception as e:
1184
- print(f"❌ فشل جلب بيانات الحيتان لـ {symbol}: {e}")
1185
- return {
1186
- 'data_available': False,
1187
- 'description': f'خطأ في جلب بيانات الحيتان: {str(e)}',
1188
- 'total_volume': 0,
1189
- 'transfer_count': 0,
1190
- 'source': 'error'
1191
- }
1192
-
1193
- async def get_fast_pass_data_async(self, symbols_with_reasons):
1194
- """جلب بيانات سريعة مع تحسين الأداء"""
1195
- timeframes = ['15m', '1h', '4h', '1d']
1196
- data = []
1197
- total = len(symbols_with_reasons)
1198
-
1199
- if total == 0:
1200
- return []
1201
-
1202
- semaphore = asyncio.Semaphore(5)
1203
-
1204
- async def fetch_symbol_data(symbol_data, index):
1205
- symbol = symbol_data['symbol']
1206
-
1207
- async with semaphore:
1208
- try:
1209
- ohlcv_data = {}
1210
- timeframes_fetched = 0
1211
-
1212
- for timeframe in timeframes:
1213
- try:
1214
- candles = await self.fetch_ohlcv_with_retry(symbol, timeframe, limit=50)
1215
-
1216
- if candles and len(candles) >= 20:
1217
- cleaned_candles = []
1218
- for candle in candles:
1219
- if len(candle) >= 6:
1220
- cleaned_candles.append([
1221
- candle[0],
1222
- float(candle[1]),
1223
- float(candle[2]),
1224
- float(candle[3]),
1225
- float(candle[4]),
1226
- float(candle[5])
1227
- ])
1228
-
1229
- if len(cleaned_candles) >= 20:
1230
- ohlcv_data[timeframe] = cleaned_candles
1231
- timeframes_fetched += 1
1232
-
1233
- await asyncio.sleep(0.05)
1234
-
1235
- except Exception as e:
1236
- ohlcv_data[timeframe] = []
1237
-
1238
- if timeframes_fetched >= 2:
1239
- return {
1240
- 'symbol': symbol,
1241
- 'ohlcv': ohlcv_data,
1242
- 'reasons': symbol_data['reasons'],
1243
- 'successful_timeframes': timeframes_fetched
1244
- }
1245
- else:
1246
- return None
1247
-
1248
- except Exception as e:
1249
- return None
1250
-
1251
- tasks = [fetch_symbol_data(symbol_data, i) for i, symbol_data in enumerate(symbols_with_reasons)]
1252
- results = await asyncio.gather(*tasks, return_exceptions=True)
1253
-
1254
- for result in results:
1255
- if result and not isinstance(result, Exception):
1256
- data.append(result)
1257
-
1258
- print(f"✅ Data fetching: {len(data)}/{total} successful")
1259
- return data
1260
-
1261
- async def get_latest_price_async(self, symbol):
1262
- """جلب آخر سعر مع إعادة المحاولة"""
1263
- max_retries = 2
1264
- for attempt in range(max_retries):
1265
- try:
1266
- ticker = await self.exchange.fetch_ticker(symbol)
1267
- price = ticker.get('last')
1268
- if price and price > 0:
1269
- return price
1270
- except Exception as e:
1271
- print(f"⚠️ فشل محاولة {attempt + 1}/{max_retries} لجلب سعر {symbol}: {e}")
1272
- if attempt < max_retries - 1:
1273
- await asyncio.sleep(0.5)
1274
-
1275
- return None
1276
 
1277
  def get_performance_stats(self):
1278
  """الحصول على إحصائيات الأداء"""
1279
  total_attempts = self.fetch_stats['successful_fetches'] + self.fetch_stats['failed_fetches']
1280
  success_rate = (self.fetch_stats['successful_fetches'] / total_attempts * 100) if total_attempts > 0 else 0
1281
 
1282
- return {
1283
  'total_attempts': total_attempts,
1284
  'successful_fetches': self.fetch_stats['successful_fetches'],
1285
  'failed_fetches': self.fetch_stats['failed_fetches'],
1286
  'rate_limit_hits': self.fetch_stats['rate_limit_hits'],
1287
  'success_rate': f"{success_rate:.1f}%",
1288
  'timestamp': datetime.now().isoformat(),
1289
- 'price_cache_size': len(self.price_cache)
1290
  }
 
 
 
 
 
1291
 
1292
- print("✅ Enhanced Data Manager Loaded - EXPLICIT MISSING DATA HANDLING - NO DEFAULT VALUES")
 
1
+ # data_manager.py
2
  import os, asyncio, httpx, json, traceback, backoff, re, time
3
  from datetime import datetime, timedelta
4
  from functools import wraps
 
8
  import numpy as np
9
  from state import MARKET_STATE_OK
10
 
11
+ # --- 🐋 نظام تتبع الحيتان المحسن مع إصلاحات APIs ---
12
  class EnhancedWhaleMonitor:
13
  def __init__(self, contracts_db=None):
14
  self.http_client = httpx.AsyncClient(timeout=15.0, limits=httpx.Limits(max_connections=50, max_keepalive_connections=10))
15
 
16
+ # 🔑 استخدام مفاتيح API من متغيرات البيئة فقط
17
  self.moralis_key = os.getenv("MORALIS_KEY")
18
  self.etherscan_key = os.getenv("ETHERSCAN_KEY")
19
+ self.infura_key = os.getenv("INFURA_KEY")
20
 
21
  self.whale_threshold_usd = 100000
22
  self.contracts_db = contracts_db or {}
23
 
24
+ # 🔄 إحصائيات استخدام APIs
25
+ self.api_usage_stats = {
26
+ 'etherscan': {
27
+ 'requests_today': 0,
28
+ 'requests_per_second': 0,
29
+ 'last_request_time': time.time(),
30
+ 'last_reset': datetime.now().date()
31
+ },
32
+ 'infura': {
33
+ 'requests_today': 0,
34
+ 'requests_per_second': 0,
35
+ 'last_request_time': time.time(),
36
+ 'last_reset': datetime.now().date()
37
+ }
38
+ }
39
+
40
+ # 🔄 مصادر RPC متعددة
41
  self.rpc_endpoints = {
42
  'ethereum': [
 
43
  'https://rpc.ankr.com/eth',
44
  'https://cloudflare-eth.com',
45
+ 'https://eth.llamarpc.com'
46
  ],
47
  'bsc': [
48
  'https://bsc-dataseed.binance.org/',
 
51
  ],
52
  'polygon': [
53
  'https://polygon-rpc.com/',
54
+ 'https://rpc-mainnet.maticvigil.com',
55
+ 'https://polygon-bor.publicnode.com'
56
  ],
57
  'arbitrum': [
58
  'https://arb1.arbitrum.io/rpc',
 
64
  ]
65
  }
66
 
67
+ # ✅ إضافة Infura إلى القائمة إذا كان المفتاح متوفرًا
68
+ if self.infura_key:
69
+ infura_endpoint = f"https://mainnet.infura.io/v3/{self.infura_key}"
70
+ self.rpc_endpoints['ethereum'].insert(0, infura_endpoint)
71
+ print(f"✅ تم تكوين Infura بنجاح - الشبكة: Ethereum")
72
+ else:
73
+ print("⚠️ مفتاح Infura غير متوفر في متغيرات البيئة - استخدام المصادر البديلة")
74
+
75
  self.current_rpc_index = {network: 0 for network in self.rpc_endpoints.keys()}
76
  self.rpc_failures = {network: 0 for network in self.rpc_endpoints.keys()}
77
 
78
  self.price_cache = {}
79
  self.last_scan_time = {}
80
+
81
+ # 🔧 تعيين رموز KuCoin الصحيحة للعملات
82
+ self.kucoin_symbols = {
83
+ 'ethereum': 'ETH',
84
+ 'bsc': 'BNB',
85
+ 'polygon': 'MATIC',
86
+ 'arbitrum': 'ETH',
87
+ 'avalanche': 'AVAX'
88
+ }
89
+
90
+ def _update_api_usage_stats(self, api_name):
91
+ """تحديث إحصائيات استخدام API"""
92
+ now = datetime.now()
93
+ current_date = now.date()
94
+
95
+ stats = self.api_usage_stats[api_name]
96
+
97
+ # 🔄 إعادة تعيين العداد اليومي إذا تغير التاريخ
98
+ if current_date != stats['last_reset']:
99
+ stats['requests_today'] = 0
100
+ stats['last_reset'] = current_date
101
+ print(f"🔄 إعادة تعيين عداد طلبات {api_name} اليومي")
102
+
103
+ # 📊 تحديث طلبات الثانية
104
+ current_time = time.time()
105
+ time_diff = current_time - stats['last_request_time']
106
+
107
+ if time_diff < 1.0:
108
+ stats['requests_per_second'] += 1
109
+ else:
110
+ stats['requests_per_second'] = 1
111
+ stats['last_request_time'] = current_time
112
+
113
+ stats['requests_today'] += 1
114
+
115
+ # 🚨 التحقق من الحدود
116
+ if api_name == 'etherscan':
117
+ if stats['requests_today'] > 90000: # تحذير عند الاقتراب من 90,000
118
+ print(f"🚨 تحذير: طلبات {api_name} اليومية تقترب من الحد ({stats['requests_today']}/100,000)")
119
+ if stats['requests_per_second'] > 4: # تحذير عند الاقتراب من 4/ثانية
120
+ print(f"🚨 تحذير: طلبات {api_name} في الثانية تقترب من الحد ({stats['requests_per_second']}/5)")
121
+
122
+ elif api_name == 'infura':
123
+ if stats['requests_today'] > 2500000:
124
+ print(f"🚨 تحذير: طلبات {api_name} اليومية تقترب من الحد ({stats['requests_today']}/3,000,000)")
125
+ if stats['requests_per_second'] > 450:
126
+ print(f"🚨 تحذير: طلبات {api_name} في الثانية تقترب من الحد ({stats['requests_per_second']}/500)")
127
+
128
+ async def _api_rate_limit_delay(self, api_name):
129
+ """تأخير ذكي لتجنب تجاوز حدود API"""
130
+ stats = self.api_usage_stats[api_name]
131
+
132
+ if api_name == 'etherscan':
133
+ # التحقق من طلبات الثانية
134
+ if stats['requests_per_second'] > 4:
135
+ delay = 0.2 * (stats['requests_per_second'] - 4)
136
+ print(f"⏳ تأخير {delay:.2f} ثانية لـ {api_name} لتجنب rate limiting")
137
+ await asyncio.sleep(delay)
138
+
139
+ # التحقق من الطلبات اليومية
140
+ if stats['requests_today'] > 95000:
141
+ print(f"🚨 تجاوز الحد اليومي لطلبات {api_name}، استخدام المصادر البديلة")
142
+ return True
143
+
144
+ elif api_name == 'infura':
145
+ if stats['requests_per_second'] > 400:
146
+ delay = 0.1 * (stats['requests_per_second'] - 400)
147
+ print(f"⏳ تأخير {delay:.2f} ثانية لـ {api_name} لتجنب rate limiting")
148
+ await asyncio.sleep(delay)
149
+
150
+ if stats['requests_today'] > 2800000:
151
+ print(f"🚨 تجاوز الحد اليومي لطلبات {api_name}، استخدام المصادر البديلة")
152
+ return True
153
+
154
+ return False
155
 
156
  def _get_next_rpc_endpoint(self, network):
157
+ """الحصول على عنوان RPC التالي"""
158
  if network not in self.rpc_endpoints:
159
  return None
160
 
161
  endpoints = self.rpc_endpoints[network]
162
  if not endpoints:
163
  return None
164
+
165
+ # 🔄 التدوير العادي للمصادر
166
  index = self.current_rpc_index[network]
167
  endpoint = endpoints[index]
 
 
168
  self.current_rpc_index[network] = (index + 1) % len(endpoints)
169
 
170
  return endpoint
171
 
172
  async def _get_native_coin_price(self, network):
173
+ """جلب الأسعار من KuCoin (عام) مع إصلاح الرموز"""
174
  now = time.time()
175
  cache_key = f"{network}_price"
176
 
 
178
  if cache_key in self.price_cache and (now - self.price_cache[cache_key]['timestamp']) < 300:
179
  return self.price_cache[cache_key]['price']
180
 
181
+ symbol = self.kucoin_symbols.get(network)
 
 
 
 
 
 
 
 
182
  if not symbol:
183
+ return await self._get_price_from_coingecko_fallback(network)
184
 
185
  try:
186
+ # 🔄 المحاولة الأولى: KuCoin (عام)
187
  price = await self._get_price_from_kucoin(symbol)
188
  if price and price > 0:
189
  self.price_cache[cache_key] = {'price': price, 'timestamp': now, 'source': 'kucoin'}
 
195
  self.price_cache[cache_key] = {'price': price, 'timestamp': now, 'source': 'coingecko'}
196
  return price
197
 
 
 
 
 
 
 
198
  return None
199
 
200
  except Exception as e:
 
202
  return None
203
 
204
  async def _get_price_from_kucoin(self, symbol):
205
+ """جلب السعر من KuCoin (عام بدون API key)"""
206
  try:
207
+ # 🔧 استخدام KuCoin بدون API keys (عام)
208
+ exchange = ccxt.kucoin({
209
+ 'sandbox': False,
210
+ 'enableRateLimit': True
211
+ })
212
+
213
+ # 🔧 المحاولة برمز USDT أولاً
214
+ trading_symbol = f"{symbol}/USDT"
215
+ try:
216
+ ticker = await exchange.fetch_ticker(trading_symbol)
217
+ price = ticker.get('last')
218
+ if price and price > 0:
219
+ print(f"✅ سعر {symbol} من KuCoin: ${price:.2f}")
220
+ await exchange.close()
221
+ return float(price)
222
+ except Exception as e:
223
+ print(f"⚠️ رمز التداول {trading_symbol} غير مدعوم في KuCoin: {e}")
224
 
225
+ await exchange.close()
 
 
 
226
  return None
227
+
228
  except Exception as e:
229
  print(f"⚠️ فشل جلب السعر من KuCoin لـ {symbol}: {e}")
230
  return None
 
235
  'ethereum': 'ethereum',
236
  'bsc': 'binancecoin',
237
  'polygon': 'matic-network',
238
+ 'arbitrum': 'ethereum',
239
  'avalanche': 'avalanche-2'
240
  }
241
 
 
244
  return None
245
 
246
  try:
247
+ await asyncio.sleep(0.5)
248
  url = f"https://api.coingecko.com/api/v3/simple/price?ids={coin_id}&vs_currencies=usd"
249
  response = await self.http_client.get(url)
250
  response.raise_for_status()
 
258
  return None
259
 
260
  async def _call_rpc_async(self, network, method, params=[]):
261
+ """اتصال RPC غير متزامن مع إدارة ذكية للطلبات"""
262
+ max_retries = 2
263
 
264
  for attempt in range(max_retries):
265
  endpoint = self._get_next_rpc_endpoint(network)
 
268
  return None
269
 
270
  try:
271
+ # 🎯 إدارة طلبات Infura
272
+ if 'infura' in endpoint and self.infura_key:
273
+ self._update_api_usage_stats('infura')
274
+
275
+ if await self._api_rate_limit_delay('infura'):
276
+ continue
277
+
278
+ print(f"🌐 استخدام Infura للشبكة {network}")
279
+
280
  payload = {"jsonrpc": "2.0", "method": method, "params": params, "id": 1}
281
 
282
+ timeout = 25.0 if method == 'eth_getBlockByNumber' else 12.0
 
283
 
284
  async with httpx.AsyncClient(timeout=timeout) as client:
285
  response = await client.post(endpoint, json=payload)
286
+
287
+ if response.status_code == 401:
288
+ print(f"🔐 خطأ مصادقة في {endpoint} - تخطي هذا المصدر")
289
+ self.rpc_failures[network] += 1
290
+ continue
291
+ elif response.status_code == 429:
292
+ print(f"⏳ Rate limit على {endpoint} - تأخير وإعادة المحاولة")
293
+ await asyncio.sleep(2 * (attempt + 1))
294
+ continue
295
+
296
  response.raise_for_status()
297
  result = response.json().get('result')
298
 
 
299
  self.rpc_failures[network] = 0
300
  return result
301
 
302
  except httpx.HTTPStatusError as e:
303
+ if e.response.status_code == 429:
304
  print(f"⚠️ Rate limit على {endpoint} لـ {network}. المحاولة {attempt + 1}/{max_retries}")
305
  self.rpc_failures[network] += 1
306
+ await asyncio.sleep(3 * (attempt + 1))
307
+ continue
308
+ elif e.response.status_code == 401:
309
+ print(f"🔐 خطأ مصادقة في {endpoint} - تخطي هذا المصدر")
310
+ self.rpc_failures[network] += 1
311
  continue
312
  else:
313
  print(f"⚠️ خطأ HTTP {e.response.status_code} في {endpoint} لـ {network}: {e}")
314
+ self.rpc_failures[network] += 1
315
 
316
  except Exception as e:
317
  print(f"⚠️ فشل اتصال RPC لـ {network} على {endpoint}: {e}")
318
  self.rpc_failures[network] += 1
319
 
 
320
  if attempt < max_retries - 1:
321
  await asyncio.sleep(1 * (attempt + 1))
322
 
 
324
  return None
325
 
326
  async def _scan_single_evm_network(self, network):
327
+ """مسح شبكة EVM واحدة مع تحسين كفاءة الطلبات"""
328
  whale_alerts = []
329
  try:
330
  price_usd = await self._get_native_coin_price(network)
 
338
 
339
  latest_block = int(latest_block_hex, 16)
340
 
341
+ blocks_to_scan = 10
342
+ scanned_blocks = 0
343
+
344
  for block_offset in range(blocks_to_scan):
345
  block_number = latest_block - block_offset
346
  if block_number < 0:
 
350
  if not block_data or 'transactions' not in block_data:
351
  continue
352
 
353
+ scanned_blocks += 1
354
+
355
  block_timestamp_hex = block_data.get('timestamp', '0x0')
356
  block_timestamp = int(block_timestamp_hex, 16)
357
  block_time = datetime.fromtimestamp(block_timestamp)
358
  time_ago = datetime.now() - block_time
359
 
 
360
  for tx in block_data.get('transactions', []):
361
  value_wei = int(tx.get('value', '0x0'), 16)
362
  if value_wei > 0:
 
376
  'transaction_type': 'native_transfer'
377
  })
378
 
379
+ if block_offset % 3 == 0:
380
+ await asyncio.sleep(0.2)
381
+
382
+ print(f"✅ مسح {network}: {scanned_blocks} كتل، {len(whale_alerts)} تنبيهات")
383
 
384
  except Exception as e:
385
  print(f"⚠️ خطأ في مسح شبكة {network}: {e}")
 
387
  return whale_alerts
388
 
389
  async def get_general_whale_activity(self):
390
+ """الوظيفة الرئيسية لمراقبة الحيتان"""
391
  print("🌊 بدء مراقبة الحيتان عبر الشبكات المتعددة...")
392
 
393
  try:
 
394
  tasks = []
395
+ networks_to_scan = ['ethereum', 'bsc']
396
+ for network in networks_to_scan:
397
  tasks.append(self._scan_single_evm_network(network))
398
 
399
  results = await asyncio.gather(*tasks, return_exceptions=True)
400
 
401
  all_alerts = []
402
+ successful_networks = 0
403
+
404
  for res in results:
405
  if isinstance(res, list):
406
  all_alerts.extend(res)
407
+ successful_networks += 1
408
 
 
409
  all_alerts.sort(key=lambda x: x['timestamp'], reverse=True)
410
 
411
  total_volume = sum(alert['value_usd'] for alert in all_alerts)
 
419
  'sentiment': 'UNKNOWN',
420
  'total_volume_usd': 0,
421
  'transaction_count': 0,
422
+ 'data_quality': 'HIGH',
423
+ 'networks_scanned': successful_networks
424
  }
425
 
 
426
  latest_alert = all_alerts[0] if all_alerts else None
427
  latest_time_info = f"آخر نشاط منذ {latest_alert['minutes_ago']:.1f} دقيقة" if latest_alert else ""
428
 
429
  sentiment = 'BULLISH' if total_volume > 5_000_000 else 'SLIGHTLY_BULLISH' if total_volume > 1_000_000 else 'NEUTRAL'
430
  critical = total_volume > 10_000_000 or any(tx['value_usd'] > 5_000_000 for tx in all_alerts)
431
 
432
+ description = f"تم اكتشاف {alert_count} معاملة حوت بإجمالي ${total_volume:,.0f} عبر {successful_networks} شبكات. {latest_time_info}"
433
  print(f"✅ {description}")
434
 
435
  return {
 
447
  'average_minutes': sum(alert['minutes_ago'] for alert in all_alerts) / len(all_alerts) if all_alerts else 0
448
  },
449
  'data_quality': 'HIGH',
450
+ 'networks_scanned': successful_networks
451
  }
452
 
453
  except Exception as e:
 
464
  }
465
 
466
  async def get_symbol_specific_whale_data(self, symbol, contract_address=None):
467
+ """جلب بيانات الحيتان الخاصة بعملة محددة"""
468
  try:
469
  base_symbol = symbol.split("/")[0] if '/' in symbol else symbol
470
 
 
494
  'source': 'error'
495
  }
496
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
497
  async def _get_combined_api_data(self, contract_address):
498
  """جلب البيانات المجمعة من مصادر API"""
499
  tasks = []
 
501
  if self.moralis_key:
502
  tasks.append(self._get_moralis_token_data(contract_address))
503
  if self.etherscan_key:
504
+ tasks.append(self._get_etherscan_token_data_v2(contract_address)) # 🔄 استخدام الإصدار V2
505
 
506
  if not tasks:
507
  return []
 
515
 
516
  return all_transfers
517
 
518
+ async def _get_etherscan_token_data_v2(self, contract_address):
519
+ """🔧 إصلاح: جلب بيانات Etherscan باستخدام الإصدار V2 من API"""
520
+ if not self.etherscan_key:
521
+ return []
522
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
523
  try:
524
+ # 🔄 تحديث إحصائيات الاستخدام
525
+ self._update_api_usage_stats('etherscan')
526
 
527
+ # التحقق من حدود الاستخدام
528
+ if await self._api_rate_limit_delay('etherscan'):
529
+ print("⚠️ تجاوز حدود Etherscan، تخطي الطلب")
530
  return []
531
 
532
+ # 🔧 استخدام معاملات الإصدار V2 حسب الوثائق
533
+ params = {
534
+ "module": "account",
535
+ "action": "tokentx",
536
+ "contractaddress": contract_address,
537
+ "page": 1,
538
+ "offset": 10, # تقليل العدد لتوفير الطلبات
539
+ "sort": "desc",
540
+ "apikey": self.etherscan_key
541
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
542
 
543
+ # 🔧 استخدام base URL الصحيح
544
+ base_url = "https://api.etherscan.io/api"
 
545
 
546
+ print(f"🔍 جلب بيانات Etherscan V2 للعقد: {contract_address[:10]}...")
 
547
 
548
+ async with httpx.AsyncClient(timeout=10.0) as client:
549
+ response = await client.get(base_url, params=params)
550
 
551
+ if response.status_code == 429:
552
+ print("⏳ تجاوز حد معدل Etherscan، تأخير الطلبات المستقبلية")
553
+ await asyncio.sleep(2)
554
+ return []
555
+
556
+ response.raise_for_status()
557
+ data = response.json()
558
+
559
+ # 🔧 التحقق من استجابة API
560
+ if data.get('status') == '1' and data.get('message') == 'OK':
561
+ result = data.get('result', [])
562
+ print(f"✅ بيانات Etherscan: {len(result)} تحويل")
563
+ return result
564
+ else:
565
+ error_message = data.get('message', 'Unknown error')
566
+ print(f"⚠️ خطأ في استجابة Etherscan: {error_message}")
567
+ return []
568
+
569
+ except httpx.HTTPStatusError as e:
570
+ print(f"⚠️ خطأ HTTP في Etherscan API: {e.response.status_code}")
571
+ return []
572
  except Exception as e:
573
+ print(f"⚠️ فشل جلب بيانات Etherscan: {e}")
574
+ return []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
575
 
576
  async def _get_moralis_token_data(self, contract_address):
577
+ """جلب بيانات Moralis"""
578
+ if not self.moralis_key:
579
+ return []
580
+
581
  try:
582
  response = await self.http_client.get(
583
  f"https://deep-index.moralis.io/api/v2/erc20/{contract_address}/transfers",
584
  headers={"X-API-Key": self.moralis_key},
585
+ params={"chain": "eth", "limit": 10} # تقليل العدد
586
  )
587
+
588
+ if response.status_code == 200:
589
+ result = response.json().get('result', [])
590
+ print(f"✅ بيانات Moralis: {len(result)} تحويل")
591
+ return result
592
+ else:
593
+ print(f"⚠️ خطأ Moralis API: {response.status_code}")
594
+ return []
595
+
596
  except Exception as e:
597
+ print(f"⚠️ Moralis API error: {e}")
598
+ return []
599
 
600
+ # ... باقي الدوال تبقى كما كانت ...
601
+
602
+ def get_api_usage_stats(self):
603
+ """الحصول على إحصائيات استخدام APIs"""
604
+ stats = {}
605
+
606
+ for api_name, api_stats in self.api_usage_stats.items():
607
+ if api_name == 'etherscan':
608
+ daily_limit = 100000
609
+ per_second_limit = 5
610
+ elif api_name == 'infura':
611
+ daily_limit = 3000000
612
+ per_second_limit = 500
613
+ else:
614
+ continue
615
+
616
+ stats[api_name] = {
617
+ 'requests_today': api_stats['requests_today'],
618
+ 'requests_per_second': api_stats['requests_per_second'],
619
+ 'daily_limit_remaining': daily_limit - api_stats['requests_today'],
620
+ 'usage_percentage': (api_stats['requests_today'] / daily_limit) * 100,
621
+ 'per_second_usage_percentage': (api_stats['requests_per_second'] / per_second_limit) * 100,
622
+ 'last_reset': api_stats['last_reset'].isoformat(),
623
+ 'api_available': getattr(self, f'{api_name}_key') is not None
624
  }
625
+
626
+ return stats
 
 
 
627
 
628
  # إنشاء نسخة عالمية
629
  whale_monitor_global = EnhancedWhaleMonitor()
 
631
  class DataManager:
632
  def __init__(self, contracts_db):
633
  self.contracts_db = contracts_db or {}
634
+
635
+ # 🔧 استخدام KuCoin بدون API keys (عام)
636
+ try:
637
+ self.exchange = ccxt.kucoin({
638
+ 'sandbox': False,
639
+ 'enableRateLimit': True
640
+ # لا حاجة لـ API keys للاستخدام العام
641
+ })
642
+ self.exchange.rateLimit = 800
643
+ print("✅ تم تهيئة KuCoin (الوضع العام)")
644
+ except Exception as e:
645
+ print(f"⚠️ فشل تهيئة KuCoin: {e}")
646
+ self.exchange = None
647
+
648
  self._whale_data_cache = {}
649
  self.http_client = None
650
  self.fetch_stats = {'successful_fetches': 0, 'failed_fetches': 0, 'rate_limit_hits': 0}
 
653
 
654
  async def initialize(self):
655
  self.http_client = httpx.AsyncClient(timeout=20.0)
656
+
657
+ # التحقق من توفر مفاتيح API
658
+ api_status = {
659
+ 'KUCOIN': '🟢 عام (بدون مفتاح)',
660
+ 'MORALIS_KEY': "🟢 متوفر" if os.getenv('MORALIS_KEY') else "🔴 غير متوفر",
661
+ 'ETHERSCAN_KEY': "🟢 متوفر" if os.getenv('ETHERSCAN_KEY') else "🔴 غير متوفر",
662
+ 'INFURA_KEY': "🟢 متوفر" if os.getenv('INFURA_KEY') else "🔴 غير متوفر"
663
+ }
664
+
665
+ print("✅ DataManager initialized - API Status:")
666
+ for key, status in api_status.items():
667
+ print(f" {key}: {status}")
668
+
669
+ # عرض حدود الاستخدام
670
+ api_limits = {
671
+ 'Etherscan': '5/ثانية, 100,000/يوم',
672
+ 'Infura': '500/ثانية, 3,000,000/يوم',
673
+ 'KuCoin': 'عام (بدون حدود)',
674
+ 'CoinGecko': '50/دقيقة'
675
+ }
676
+
677
+ print("📊 حدود استخدام APIs:")
678
+ for api, limit in api_limits.items():
679
+ print(f" {api}: {limit}")
680
 
681
  async def close(self):
682
  if self.http_client:
683
  await self.http_client.aclose()
684
+ if self.exchange:
685
+ await self.exchange.close()
686
 
687
  async def get_sentiment_safe_async(self):
688
  """جلب بيانات المشاعر مع مصادر احتياطية"""
 
710
  if attempt < max_retries - 1:
711
  await asyncio.sleep(1)
712
 
 
713
  return None
714
 
715
  async def get_market_context_async(self):
716
+ """جلب سياق السوق مع تتبع استخدام APIs"""
717
  max_retries = 2
718
  for attempt in range(max_retries):
719
  try:
720
+ print(f"📊 جلب سياق السوق - المحاولة {attempt + 1}/{max_retries}")
721
 
722
  sentiment_task = asyncio.wait_for(self.get_sentiment_safe_async(), timeout=10)
723
  price_task = asyncio.wait_for(self._get_prices_with_fallback(), timeout=15)
 
730
  price_data = results[1] if not isinstance(results[1], Exception) else {}
731
  general_whale_activity = results[2] if not isinstance(results[2], Exception) else None
732
 
 
 
 
 
 
733
  bitcoin_price = price_data.get('bitcoin')
734
  ethereum_price = price_data.get('ethereum')
735
 
 
 
 
 
 
 
736
  if bitcoin_price is None or ethereum_price is None:
737
  if attempt < max_retries - 1:
738
  await asyncio.sleep(2)
 
742
 
743
  market_trend = self._determine_market_trend(bitcoin_price, sentiment_data, general_whale_activity)
744
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
745
  market_context = {
746
  'timestamp': datetime.now().isoformat(),
747
  'bitcoin_price_usd': bitcoin_price,
 
754
  'critical_alert': False,
755
  'sentiment': 'UNKNOWN'
756
  },
 
757
  'market_trend': market_trend,
758
  'btc_sentiment': self._get_btc_sentiment(bitcoin_price),
759
  'data_sources': {
 
761
  'sentiment': sentiment_data is not None,
762
  'general_whale_data': general_whale_activity.get('data_available', False) if general_whale_activity else False
763
  },
764
+ 'data_quality': 'HIGH',
765
+ 'api_usage': self.whale_monitor.get_api_usage_stats()
 
766
  }
767
 
768
+ print(f"📊 سياق السوق: BTC=${bitcoin_price:,.0f}, ETH=${ethereum_price:,.0f}")
 
 
769
 
770
  return market_context
771
 
 
 
 
 
772
  except Exception as e:
773
  print(f"❌ فشل محاولة {attempt + 1}/{max_retries} لجلب سياق السوق: {e}")
774
  if attempt < max_retries - 1:
 
777
  return self._get_minimal_market_context()
778
 
779
  def _get_btc_sentiment(self, bitcoin_price):
780
+ """تحديد اتجاه البيتكوين"""
781
  if bitcoin_price is None:
782
  return 'UNKNOWN'
783
  elif bitcoin_price > 60000:
 
788
  return 'NEUTRAL'
789
 
790
  async def _get_prices_with_fallback(self):
791
+ """جلب الأسعار مع KuCoin العام"""
792
  try:
793
+ prices = await self._get_prices_from_kucoin_safe()
794
  if prices.get('bitcoin') and prices.get('ethereum'):
795
  return prices
796
 
 
804
  print(f"❌ فشل جلب الأسعار: {e}")
805
  return {'bitcoin': None, 'ethereum': None}
806
 
807
+ async def _get_prices_from_kucoin_safe(self):
808
+ """جلب الأسعار من KuCoin (عام)"""
809
+ if not self.exchange:
810
+ return {'bitcoin': None, 'ethereum': None}
811
+
812
  try:
813
+ prices = {'bitcoin': None, 'ethereum': None}
814
+
815
+ try:
816
+ btc_ticker = await self.exchange.fetch_ticker('BTC/USDT')
817
+ btc_price = float(btc_ticker.get('last', 0)) if btc_ticker.get('last') else None
818
+ if btc_price and btc_price > 0:
819
+ prices['bitcoin'] = btc_price
820
+ self.price_cache['bitcoin'] = btc_price
821
+ print(f"✅ BTC من KuCoin: ${btc_price:.0f}")
822
+ except Exception as e:
823
+ print(f"⚠️ فشل جلب سعر BTC من KuCoin: {e}")
824
 
825
+ try:
826
+ eth_ticker = await self.exchange.fetch_ticker('ETH/USDT')
827
+ eth_price = float(eth_ticker.get('last', 0)) if eth_ticker.get('last') else None
828
+ if eth_price and eth_price > 0:
829
+ prices['ethereum'] = eth_price
830
+ self.price_cache['ethereum'] = eth_price
831
+ print(f"✅ ETH من KuCoin: ${eth_price:.0f}")
832
+ except Exception as e:
833
+ print(f"⚠️ فشل جلب سعر ETH من KuCoin: {e}")
834
 
835
+ return prices
 
 
 
 
 
 
836
 
837
  except Exception as e:
838
  print(f"⚠️ فشل جلب الأسعار من KuCoin: {e}")
 
864
  return {'bitcoin': None, 'ethereum': None}
865
 
866
  def _get_minimal_market_context(self):
867
+ """إرجاع سياق سوق أساسي"""
868
  return {
869
  'timestamp': datetime.now().isoformat(),
870
  'data_available': False,
 
887
  }
888
 
889
  def _determine_market_trend(self, bitcoin_price, sentiment_data, whale_activity):
890
+ """تحديد اتجاه السوق"""
891
  try:
892
  if bitcoin_price is None:
893
  return "UNKNOWN"
 
936
  print(f"⚠️ خطأ في تحديد اتجاه السوق: {e}")
937
  return "UNKNOWN"
938
 
939
+ # ... باقي دوال DataManager تبقى كما هي ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
940
 
941
  def get_performance_stats(self):
942
  """الحصول على إحصائيات الأداء"""
943
  total_attempts = self.fetch_stats['successful_fetches'] + self.fetch_stats['failed_fetches']
944
  success_rate = (self.fetch_stats['successful_fetches'] / total_attempts * 100) if total_attempts > 0 else 0
945
 
946
+ stats = {
947
  'total_attempts': total_attempts,
948
  'successful_fetches': self.fetch_stats['successful_fetches'],
949
  'failed_fetches': self.fetch_stats['failed_fetches'],
950
  'rate_limit_hits': self.fetch_stats['rate_limit_hits'],
951
  'success_rate': f"{success_rate:.1f}%",
952
  'timestamp': datetime.now().isoformat(),
953
+ 'exchange_available': self.exchange is not None
954
  }
955
+
956
+ api_stats = self.whale_monitor.get_api_usage_stats()
957
+ stats['api_usage'] = api_stats
958
+
959
+ return stats
960
 
961
+ print("✅ Enhanced Data Manager Loaded - KUCOIN PUBLIC + ETHERSCAN V2 FIX + API RATE LIMITING")