Riy777 commited on
Commit
4ace337
·
1 Parent(s): d74f533

Update data_manager.py

Browse files
Files changed (1) hide show
  1. data_manager.py +184 -593
data_manager.py CHANGED
@@ -1,4 +1,4 @@
1
- # data_manager.py (Updated to V7.9 - Re-balanced Spark Filter Weights)
2
  import os
3
  import asyncio
4
  import httpx
@@ -14,30 +14,29 @@ import pandas as pd
14
  try:
15
  import pandas_ta as ta
16
  except ImportError:
17
- print("⚠️ مكتبة pandas_ta غير موجودة، فلتر الغربلة المتقدم سيفشل.")
18
  ta = None
19
 
 
20
  from ml_engine.indicators import AdvancedTechnicalAnalyzer
21
  from ml_engine.monte_carlo import MonteCarloAnalyzer
22
- # (V8-MODIFICATION) استيراد المحرك الصحيح (V8)
23
  from ml_engine.patterns import ChartPatternAnalyzer
 
 
24
 
25
  logging.getLogger("httpx").setLevel(logging.WARNING)
26
  logging.getLogger("httpcore").setLevel(logging.WARNING)
27
 
28
  class DataManager:
29
- # (V8-MODIFICATION) قبول r2_service
30
  def __init__(self, contracts_db, whale_monitor, r2_service=None):
31
  self.contracts_db = contracts_db or {}
32
  self.whale_monitor = whale_monitor
33
- self.r2_service = r2_service # (V8-MODIFICATION) الإضافة الجديدة
34
 
35
  try:
36
  self.exchange = ccxt.kucoin({
37
- 'sandbox': False,
38
- 'enableRateLimit': True,
39
- 'timeout': 30000,
40
- 'verbose': False,
41
  })
42
  print("✅ تم تهيئة اتصال KuCoin بنجاح")
43
  except Exception as e:
@@ -48,100 +47,88 @@ class DataManager:
48
  self.market_cache = {}
49
  self.last_market_load = None
50
 
 
51
  self.technical_analyzer = AdvancedTechnicalAnalyzer()
52
  self.monte_carlo_analyzer = MonteCarloAnalyzer()
53
- self.pattern_analyzer = None # (V8-MODIFICATION) سيتم تهيئته في initialize
 
 
54
 
55
  async def initialize(self):
56
  self.http_client = httpx.AsyncClient(timeout=30.0)
57
  await self._load_markets()
58
 
59
- # (V8-MODIFICATION) تهيئة محرك الأنماط V8 (ML-Based)
60
  print(" > [DataManager] تهيئة محرك الأنماط V8 (ML-Based)...")
61
  try:
62
  self.pattern_analyzer = ChartPatternAnalyzer(r2_service=self.r2_service)
63
- await self.pattern_analyzer.initialize() # (تحميل النموذج والمقياس من R2)
64
  except Exception as e:
65
  print(f"❌ [DataManager] فشل تهيئة محرك الأنماط V8: {e}")
66
- # (العودة للوضع الآمن إذا فشل التحميل)
67
  self.pattern_analyzer = ChartPatternAnalyzer(r2_service=None)
68
- # --- (نهاية الإضافة) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
- print("✅ DataManager initialized - V7.9 (Re-balanced Spark Weights)")
71
 
72
  async def _load_markets(self):
73
  try:
74
- if not self.exchange:
75
- return
76
-
77
  print("🔄 جلب أحدث بيانات الأسواق من KuCoin...")
78
  self.exchange.load_markets()
79
  self.market_cache = self.exchange.markets
80
  self.last_market_load = datetime.now()
81
  print(f"✅ تم تحميل {len(self.market_cache)} سوق من KuCoin")
82
-
83
- except Exception as e:
84
- print(f"❌ ف��ل تحميل بيانات الأسواق: {e}")
85
 
86
  async def close(self):
87
- if self.http_client and not self.http_client.is_closed:
88
- await self.http_client.aclose()
89
- print(" ✅ DataManager: http_client closed.")
90
-
91
  if self.exchange:
92
- try:
93
- await self.exchange.close()
94
- print(" ✅ DataManager: ccxt.kucoin exchange closed.")
95
- except Exception as e:
96
- print(f" ⚠️ DataManager: Error closing ccxt.kucoin: {e}")
97
-
98
  async def get_market_context_async(self):
99
  try:
100
  sentiment_data = await self.get_sentiment_safe_async()
101
  price_data = await self._get_prices_with_fallback()
102
-
103
- bitcoin_price = price_data.get('bitcoin')
104
- ethereum_price = price_data.get('ethereum')
105
-
106
- market_context = {
107
  'timestamp': datetime.now().isoformat(),
108
- 'bitcoin_price_usd': bitcoin_price,
109
- 'ethereum_price_usd': ethereum_price,
110
  'fear_and_greed_index': sentiment_data.get('feargreed_value') if sentiment_data else None,
111
  'sentiment_class': sentiment_data.get('feargreed_class') if sentiment_data else 'NEUTRAL',
112
  'market_trend': self._determine_market_trend(bitcoin_price, sentiment_data),
113
  'btc_sentiment': self._get_btc_sentiment(bitcoin_price),
114
  'data_quality': 'HIGH' if bitcoin_price and ethereum_price else 'LOW'
115
  }
116
-
117
- return market_context
118
-
119
- except Exception as e:
120
- return self._get_minimal_market_context()
121
 
 
122
  async def get_sentiment_safe_async(self):
123
  try:
124
  async with httpx.AsyncClient(timeout=10) as client:
125
  response = await client.get("https://api.alternative.me/fng/")
126
- response.raise_for_status()
127
- data = response.json()
128
-
129
- if 'data' not in data or not data['data']:
130
- raise ValueError("بيانات المشاعر غير متوفرة")
131
-
132
  latest_data = data['data'][0]
133
- return {
134
- "feargreed_value": int(latest_data['value']),
135
- "feargreed_class": latest_data['value_classification'],
136
- "source": "alternative.me",
137
- "timestamp": datetime.now().isoformat()
138
- }
139
- except Exception as e:
140
- return None
141
-
142
  def _determine_market_trend(self, bitcoin_price, sentiment_data):
143
- if bitcoin_price is None:
144
- return "UNKNOWN"
145
  if bitcoin_price > 60000: score = 1
146
  elif bitcoin_price < 55000: score = -1
147
  else: score = 0
@@ -152,36 +139,27 @@ class DataManager:
152
  if score >= 1: return "bull_market"
153
  elif score <= -1: return "bear_market"
154
  else: return "sideways_market"
155
-
156
  def _get_btc_sentiment(self, bitcoin_price):
157
  if bitcoin_price is None: return 'UNKNOWN'
158
  elif bitcoin_price > 60000: return 'BULLISH'
159
  elif bitcoin_price < 55000: return 'BEARISH'
160
  else: return 'NEUTRAL'
161
-
162
  async def _get_prices_with_fallback(self):
163
  try:
164
  prices = await self._get_prices_from_kucoin_safe()
165
- if prices.get('bitcoin') and prices.get('ethereum'):
166
- return prices
167
  return await self._get_prices_from_coingecko()
168
- except Exception as e:
169
- return {'bitcoin': None, 'ethereum': None}
170
-
171
  async def _get_prices_from_kucoin_safe(self):
172
  if not self.exchange: return {'bitcoin': None, 'ethereum': None}
173
  try:
174
  prices = {'bitcoin': None, 'ethereum': None}
175
- btc_ticker = self.exchange.fetch_ticker('BTC/USDT')
176
- btc_price = float(btc_ticker.get('last', 0)) if btc_ticker.get('last') else None
177
  if btc_price and btc_price > 0: prices['bitcoin'] = btc_price
178
- eth_ticker = self.exchange.fetch_ticker('ETH/USDT')
179
- eth_price = float(eth_ticker.get('last', 0)) if eth_ticker.get('last') else None
180
  if eth_price and eth_price > 0: prices['ethereum'] = eth_price
181
  return prices
182
- except Exception as e:
183
- return {'bitcoin': None, 'ethereum': None}
184
-
185
  async def _get_prices_from_coingecko(self):
186
  try:
187
  await asyncio.sleep(0.5)
@@ -189,32 +167,17 @@ class DataManager:
189
  headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'application/json'}
190
  async with httpx.AsyncClient(headers=headers) as client:
191
  response = await client.get(url, timeout=10)
192
- if response.status_code == 429:
193
- await asyncio.sleep(2)
194
- response = await client.get(url, timeout=10)
195
- response.raise_for_status()
196
- data = response.json()
197
- btc_price = data.get('bitcoin', {}).get('usd')
198
- eth_price = data.get('ethereum', {}).get('usd')
199
- if btc_price and eth_price:
200
- return {'bitcoin': btc_price, 'ethereum': eth_price}
201
- else:
202
- return {'bitcoin': None, 'ethereum': None}
203
- except Exception as e:
204
- return {'bitcoin': None, 'ethereum': None}
205
-
206
  def _get_minimal_market_context(self):
207
- return {
208
- 'timestamp': datetime.now().isoformat(),
209
- 'data_available': False,
210
- 'market_trend': 'UNKNOWN',
211
- 'btc_sentiment': 'UNKNOWN',
212
- 'data_quality': 'LOW'
213
- }
214
 
215
-
216
  def _create_dataframe(self, candles: List) -> pd.DataFrame:
217
- """(V7.1) دالة مساعدة لإنشاء DataFrame لتحليل 1H"""
218
  try:
219
  if not candles: return pd.DataFrame()
220
  df = pd.DataFrame(candles, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
@@ -224,583 +187,211 @@ class DataManager:
224
  df.sort_index(inplace=True)
225
  return df
226
  except Exception as e:
227
- print(f"❌ خطأ في إنشاء DataFrame لمرشح 1H: {e}")
228
  return pd.DataFrame()
229
 
230
- def _get_mc_score_for_filter(self, analysis: Dict) -> float:
231
- """(V7.4) (دالة مساعدة) لحساب درجة مونت كارلو للفلتر"""
232
- mc_distribution = analysis.get('monte_carlo_distribution')
233
- monte_carlo_score = 0
234
-
235
- if mc_distribution and mc_distribution.get('error') is None:
236
- prob_gain = mc_distribution.get('probability_of_gain', 0)
237
- var_95_value = mc_distribution.get('risk_metrics', {}).get('VaR_95_value', 0)
238
- current_price = analysis.get('current_price', 1)
239
-
240
- if current_price > 0:
241
- normalized_var = var_95_value / current_price
242
- risk_penalty = 1.0
243
- if normalized_var > 0.05: risk_penalty = 0.5
244
- elif normalized_var > 0.03: risk_penalty = 0.8
245
-
246
- normalized_prob_score = max(0.0, (prob_gain - 0.5) * 2)
247
- monte_carlo_score = normalized_prob_score * risk_penalty
248
- return monte_carlo_score
249
-
250
- def _calculate_1h_momentum_burst_score(self, analysis: Dict) -> float:
251
- """
252
- (محدث V7.7) - الفلتر الأول: "فلتر شمس منتصف الظهر" (الانفجار)
253
- """
254
- try:
255
- ohlcv_candles = analysis.get('ohlcv_1h', {}).get('1h', [])
256
- if not ohlcv_candles or len(ohlcv_candles) < 30:
257
- return 0.0
258
-
259
- closes_1h = [c[4] for c in ohlcv_candles]
260
- if len(closes_1h) > 20:
261
- std_dev = np.std(closes_1h[-20:])
262
- if std_dev < 1e-5:
263
- return 0.0 # (واقي العملات المستقرة)
264
-
265
- if ta is None: return 0.0
266
- df = self._create_dataframe(ohlcv_candles)
267
- if df.empty: return 0.0
268
-
269
- # 1. حساب مؤشرات الانفجار
270
- volume = df['volume']
271
- vol_ma = ta.sma(volume, length=20)
272
- if vol_ma is None or vol_ma.empty: return 0.0
273
- current_volume = volume.iloc[-1]
274
- avg_volume = vol_ma.iloc[-1]
275
- adx_data = ta.adx(df['high'], df['low'], df['close'], length=14)
276
- current_adx = adx_data['ADX_14'].iloc[-1] if adx_data is not None and not adx_data.empty else 0
277
-
278
- # 2. جلب المؤشرات الأساسية (المحسوبة مسبقاً)
279
- indicators = analysis.get('advanced_indicators', {}).get('1h', {})
280
- rsi = indicators.get('rsi', 50)
281
-
282
- # 3. جلب درجة مونت كارلو (المحسوبة مسبقاً)
283
- monte_carlo_score = self._get_mc_score_for_filter(analysis)
284
-
285
- # 4. جلب درجة الأنماط (المحسوبة مسبقاً)
286
- pattern_confidence = analysis.get('pattern_analysis', {}).get('pattern_confidence', 0)
287
-
288
- # --- (منطق فلترة الانفجار) ---
289
- VOL_MULTIPLIER = 1.75 # (صارم)
290
- ADX_THRESHOLD = 25.0 # (صارم)
291
- RSI_MIN = 60 # (صارم)
292
- RSI_MAX = 85
293
-
294
- vol_score = 0.0
295
- if avg_volume > 0:
296
- vol_score = min(1.0, max(0.0, (current_volume / avg_volume) / VOL_MULTIPLIER))
297
-
298
- adx_score = min(1.0, max(0.0, (current_adx - ADX_THRESHOLD) / 15.0))
299
-
300
- rsi_score = 0.0
301
- if RSI_MIN <= rsi <= RSI_MAX:
302
- rsi_score = 1.0
303
- elif rsi > RSI_MAX:
304
- rsi_score = 0.5
305
-
306
- # (الأوزان لفلتر الانفجار)
307
- WEIGHT_VOL = 0.30
308
- WEIGHT_ADX = 0.30
309
- WEIGHT_RSI = 0.15
310
- WEIGHT_MC = 0.15
311
- WEIGHT_PATTERN = 0.10
312
-
313
- final_score = (
314
- (vol_score * WEIGHT_VOL) +
315
- (adx_score * WEIGHT_ADX) +
316
- (rsi_score * WEIGHT_RSI) +
317
- (monte_carlo_score * WEIGHT_MC) +
318
- (pattern_confidence * WEIGHT_PATTERN)
319
- )
320
-
321
- return min(max(final_score, 0.0), 1.0)
322
-
323
- except Exception as e:
324
- return 0.0
325
-
326
- # 🔴 --- START OF CHANGE (V7.9) --- 🔴
327
- def _calculate_1h_reversal_spark_score(self, analysis: Dict) -> float:
328
- """
329
- (محدث V7.9) - الفلتر الثاني: "فلتر شرارة الانعكاس" (أوزان معدلة)
330
- """
331
- try:
332
- # (1. واقي العملات المستقرة)
333
- ohlcv_candles = analysis.get('ohlcv_1h', {}).get('1h', [])
334
- if not ohlcv_candles or len(ohlcv_candles) < 30:
335
- return 0.0
336
-
337
- closes_1h = [c[4] for c in ohlcv_candles]
338
- if len(closes_1h) > 20:
339
- std_dev = np.std(closes_1h[-20:])
340
- if std_dev < 1e-5:
341
- return 0.0
342
-
343
- if ta is None: return 0.0
344
- df = self._create_dataframe(ohlcv_candles)
345
- if df.empty: return 0.0
346
-
347
- # 2. حساب مؤشرات "الشرارة"
348
- volume = df['volume']
349
- vol_ma = ta.sma(volume, length=20)
350
- if vol_ma is None or vol_ma.empty: return 0.0
351
- current_volume = volume.iloc[-1]
352
- avg_volume = vol_ma.iloc[-1]
353
- adx_data = ta.adx(df['high'], df['low'], df['close'], length=14)
354
- current_adx = adx_data['ADX_14'].iloc[-1] if adx_data is not None and not adx_data.empty else 0
355
-
356
- # 3. جلب المؤشرات الأساسية (المحسوبة مسبقاً)
357
- indicators = analysis.get('advanced_indicators', {}).get('1h', {})
358
- rsi = indicators.get('rsi', 50)
359
-
360
- # 4. جلب درجة مونت كارلو (المحسوبة مسبقاً)
361
- monte_carlo_score = self._get_mc_score_for_filter(analysis)
362
-
363
- # 5. جلب درجة الأنماط (المحسوبة مسبقاً)
364
- pattern_analysis = analysis.get('pattern_analysis', {})
365
-
366
- # --- (منطق فلترة "الشرارة") ---
367
-
368
- pattern_score = 0.0
369
- if pattern_analysis.get('predicted_direction') == 'up':
370
- pattern_score = pattern_analysis.get('pattern_confidence', 0)
371
-
372
- # (RSI في منطقة الاشتعال 45-60)
373
- RSI_IGNITION_MIN = 45
374
- RSI_IGNITION_MAX = 60
375
- rsi_score = 0.0
376
- if RSI_IGNITION_MIN <= rsi <= RSI_IGNITION_MAX:
377
- rsi_score = 1.0 # (مثالي)
378
- elif rsi > RSI_IGNITION_MAX:
379
- rsi_score = 0.5 # (لا بأس)
380
-
381
- # (ADX منخفض، يدل على "تجميع" أو "نطاق")
382
- ADX_COIL_THRESHOLD = 20.0
383
- adx_score = 0.0
384
- if current_adx < ADX_COIL_THRESHOLD:
385
- adx_score = 1.0 # (مثالي)
386
-
387
- # (الحجم بدأ في الارتفاع (أعلى من المتوسط))
388
- vol_score = 0.0
389
- if avg_volume > 0 and (current_volume / avg_volume) > 1.1:
390
- vol_score = 0.5 # (مجرد اهتمام بسيط)
391
-
392
- # (الأوزان الجديدة V7.9 - بناءً على طلبك)
393
- WEIGHT_PATTERN = 0.10 # (تم الخفض)
394
- WEIGHT_RSI_SPARK = 0.35 # (تم الرفع)
395
- WEIGHT_ADX_COIL = 0.35 # (تم الرفع)
396
- WEIGHT_MC = 0.10
397
- WEIGHT_VOL = 0.10
398
- # (المجموع = 1.0)
399
-
400
- final_score = (
401
- (pattern_score * WEIGHT_PATTERN) +
402
- (rsi_score * WEIGHT_RSI_SPARK) +
403
- (adx_score * WEIGHT_ADX_COIL) +
404
- (monte_carlo_score * WEIGHT_MC) +
405
- (vol_score * WEIGHT_VOL)
406
- )
407
-
408
- return min(max(final_score, 0.0), 1.0)
409
-
410
- except Exception as e:
411
- return 0.0
412
- # 🔴 --- END OF CHANGE (V7.9) --- 🔴
413
-
414
 
 
415
  async def layer1_rapid_screening(self) -> List[Dict[str, Any]]:
416
  """
417
- الطبقة 1: فحص سريع - (محدث بالكامل V7.9)
 
 
418
  """
419
- print("📊 الطبقة 1 (V7.9): بدء الغربلة (الكاشف المزدوج + أوزان الشرارة المعدلة)...")
420
 
 
 
 
 
421
  # الخطوة 1: جلب أفضل 100 عملة حسب الحجم
422
  volume_data = await self._get_volume_data_optimal()
 
423
  if not volume_data:
424
- volume_data = await self._get_volume_data_direct_api()
425
-
426
- if not volume_data:
427
- print("❌ فشل جلب بيانات الأحجام للطبقة 1")
428
  return []
429
 
430
  volume_data.sort(key=lambda x: x['dollar_volume'], reverse=True)
431
  top_100_by_volume = volume_data[:100]
 
432
 
433
- print(f"✅ تم تحديد أفضل {len(top_100_by_volume)} عملة. بدء تشغيل الكاشف المزدوج (1H)...")
434
-
435
- final_candidates = []
436
-
437
- batch_size = 20
438
- for i in range(0, len(top_100_by_volume), batch_size):
439
- batch_symbols_data = top_100_by_volume[i:i + batch_size]
440
- batch_symbols = [s['symbol'] for s in batch_symbols_data]
441
-
442
- print(f" 🔄 معالجة دفعة {int(i/batch_size) + 1}/{(len(top_100_by_volume) + batch_size - 1) // batch_size} ({len(batch_symbols)} عملة)...")
443
-
444
- # الخطوة 2: جلب بيانات 1H بالتوازي
445
- tasks = [self._fetch_1h_ohlcv_for_screening(symbol) for symbol in batch_symbols]
446
- results_candles = await asyncio.gather(*tasks, return_exceptions=True)
447
 
448
- analysis_tasks = []
449
- valid_symbol_data = []
450
-
451
- for j, (candles) in enumerate(results_candles):
452
- symbol_data = batch_symbols_data[j]
453
- symbol = symbol_data['symbol']
454
-
455
- if isinstance(candles, Exception) or not candles or len(candles) < 50:
456
- continue
457
-
458
- ohlcv_1h_only = {'1h': candles}
459
- symbol_data['ohlcv_1h'] = ohlcv_1h_only
460
- symbol_data['current_price'] = candles[-1][4]
461
- analysis_tasks.append(self._run_mini_detector(symbol_data))
462
- valid_symbol_data.append(symbol_data)
463
-
464
- if not analysis_tasks:
465
- continue
466
-
467
- analysis_results = await asyncio.gather(*analysis_tasks, return_exceptions=True)
468
-
469
- # (تطبيق منطق الفلتر المزدوج)
470
- for j, (analysis_output) in enumerate(analysis_results):
471
- symbol_data = valid_symbol_data[j]
472
- symbol = symbol_data['symbol']
473
-
474
- if isinstance(analysis_output, Exception):
475
- print(f" - {symbol}: فشل الكاشف المصغر ({analysis_output})")
476
- continue
477
-
478
- analysis_output['ohlcv_1h'] = symbol_data['ohlcv_1h']
479
- analysis_output['symbol'] = symbol
480
-
481
- # (1. محاولة فلتر "الانفجار" أولاً)
482
- burst_score = self._calculate_1h_momentum_burst_score(analysis_output)
483
-
484
- final_score = burst_score
485
- candidacy_reason = "1H_MOMENTUM_BURST" # (السبب الافتراضي)
486
-
487
- # (2. إذا فشل فلتر الانفجار، جرب فلتر "الشرارة")
488
- if burst_score < 0.50:
489
- # (استخدام V7.9 بالأوزان الجديدة)
490
- spark_score = self._calculate_1h_reversal_spark_score(analysis_output)
491
-
492
- final_score = spark_score # (استخدام درجة الشرارة)
493
- candidacy_reason = "1H_REVERSAL_SPARK" # (تغيير السبب)
494
-
495
- # (3. التحقق من العتبة النهائية 0.50)
496
- if final_score >= 0.50:
497
- print(f" ✅ {symbol}: نجح ({candidacy_reason} | الدرجة: {final_score:.2f})")
498
- symbol_data['layer1_score'] = final_score
499
- symbol_data['reasons_for_candidacy'] = [candidacy_reason]
500
-
501
- if 'ohlcv_1h' in symbol_data: del symbol_data['ohlcv_1h']
502
-
503
- final_candidates.append(symbol_data)
504
-
505
- print(f"🎯 اكتملت الغربلة (V7.9). تم تأهيل {len(final_candidates)} عملة من أصل 100 للطبقة 2.")
506
-
507
- if final_candidates:
508
- print("🏆 المرشحون الناجحون:")
509
- for k, candidate in enumerate(final_candidates[:15]):
510
- score = candidate.get('layer1_score', 0)
511
- reason = candidate.get('reasons_for_candidacy', ['N/A'])[0]
512
- print(f" {k+1:2d}. {candidate['symbol']}: (الدرجة: {score:.2f}) | السبب: {reason}")
513
 
514
- return final_candidates
515
-
516
- async def _run_mini_detector(self, symbol_data: Dict) -> Dict:
517
- """(محدث V7.8) يشغل المحللات + يطبع نتيجة الأنماط V8"""
518
- ohlcv_1h = symbol_data.get('ohlcv_1h')
519
- current_price = symbol_data.get('current_price')
520
- symbol = symbol_data.get('symbol', 'UNKNOWN') # (للطباعة)
521
-
522
- df = self._create_dataframe(ohlcv_1h.get('1h'))
523
- if df.empty:
524
- raise ValueError("DataFrame فارغ لتحليل 1H")
525
-
526
- analysis_dict = {'current_price': current_price}
527
-
528
- task_indicators = self.technical_analyzer.calculate_all_indicators(df, '1h')
529
- task_mc = self.monte_carlo_analyzer.generate_1h_price_distribution(ohlcv_1h)
530
- task_pattern = self.pattern_analyzer.detect_chart_patterns(ohlcv_1h)
531
 
532
- results = await asyncio.gather(task_mc, task_pattern, return_exceptions=True)
533
 
534
- analysis_dict['advanced_indicators'] = {'1h': task_indicators}
 
 
 
 
 
 
 
 
 
 
 
535
 
536
- if not isinstance(results[0], Exception):
537
- analysis_dict['monte_carlo_distribution'] = results[0]
 
 
 
 
 
 
538
 
539
- # (إضافة طباعة تشخيصية لنموذج الأنماط V8)
540
- if not isinstance(results[1], Exception):
541
- pattern_result = results[1]
542
- analysis_dict['pattern_analysis'] = pattern_result
543
 
544
- pattern_name = pattern_result.get('pattern_detected', 'no_clear_pattern')
545
- confidence = pattern_result.get('pattern_confidence', 0)
546
- tf = pattern_result.get('timeframe', 'N/A')
 
 
 
 
 
547
 
548
- if pattern_name != "Neutral / No Pattern" and confidence > 0.5:
549
- print(f" 💡 [ML Pattern] {symbol}: نمط مُكتشف! "
550
- f"'{pattern_name}' (الثقة: {confidence:.2f}) على إطار {tf}")
551
- elif pattern_name == "Neutral / No Pattern":
552
- pass
553
- else:
554
- print(f" - [ML Pattern] {symbol}: نمط ضعيف. "
555
- f"'{pattern_name}' (الثقة: {confidence:.2f}) على إطار {tf}")
556
 
 
 
 
 
 
557
  else:
558
- print(f" [ML Pattern] {symbol}: فشل تحليل الأنماط ({results[1]})")
559
- analysis_dict['pattern_analysis'] = {}
560
-
561
- return analysis_dict
562
 
 
 
563
 
564
- async def _fetch_1h_ohlcv_for_screening(self, symbol: str) -> List:
565
- """(V7.1) جلب 100 شمعة لإطار الساعة (1H) للغربلة السريعة"""
566
  try:
567
- ohlcv_data = self.exchange.fetch_ohlcv(symbol, '1h', limit=100)
568
-
569
- if not ohlcv_data or len(ohlcv_data) < 50:
570
- return None
571
  return ohlcv_data
572
- except Exception:
573
- return None
574
 
 
575
  async def _get_volume_data_optimal(self) -> List[Dict[str, Any]]:
576
  try:
577
  if not self.exchange: return []
578
- tickers = self.exchange.fetch_tickers()
579
- volume_data = []
580
  for symbol, ticker in tickers.items():
581
  if not symbol.endswith('/USDT') or not ticker.get('active', True): continue
582
- current_price = ticker.get('last', 0)
583
- quote_volume = ticker.get('quoteVolume', 0)
584
  if current_price is None or current_price <= 0: continue
585
- if quote_volume is not None and quote_volume > 0:
586
- dollar_volume = quote_volume
587
  else:
588
  base_volume = ticker.get('baseVolume', 0)
589
  if base_volume is None: continue
590
  dollar_volume = base_volume * current_price
591
  if dollar_volume is None or dollar_volume < 50000: continue
592
-
593
- price_change_24h = ticker.get('percentage', 0) or 0
594
- if price_change_24h is None: price_change_24h = 0
595
-
596
- volume_data.append({
597
- 'symbol': symbol, 'dollar_volume': dollar_volume,
598
- 'current_price': current_price, 'volume_24h': ticker.get('baseVolume', 0) or 0,
599
- 'price_change_24h': price_change_24h
600
- })
601
- print(f"✅ تم معالجة {len(volume_data)} عملة في الطريقة المثلى (لجلب الحجم)")
602
  return volume_data
603
- except Exception as e:
604
- print(f"❌ خطأ في جلب بيانات الحجم المثلى: {e}")
605
- return []
606
-
607
  async def _get_volume_data_direct_api(self) -> List[Dict[str, Any]]:
608
  try:
609
  url = "https://api.kucoin.com/api/v1/market/allTickers"
610
  async with httpx.AsyncClient(timeout=15) as client:
611
- response = await client.get(url)
612
- response.raise_for_status()
613
- data = response.json()
614
  if data.get('code') != '200000': raise ValueError(f"استجابة API غير متوقعة: {data.get('code')}")
615
- tickers = data['data']['ticker']
616
- volume_data = []
617
  for ticker in tickers:
618
  symbol = ticker['symbol']
619
  if not symbol.endswith('USDT'): continue
620
  formatted_symbol = symbol.replace('-', '/')
621
  try:
622
- vol_value = ticker.get('volValue')
623
- last_price = ticker.get('last')
624
- change_rate = ticker.get('changeRate')
625
- vol = ticker.get('vol')
626
  if vol_value is None or last_price is None or change_rate is None or vol is None: continue
627
- dollar_volume = float(vol_value) if vol_value else 0
628
- current_price = float(last_price) if last_price else 0
629
- price_change = (float(change_rate) * 100) if change_rate else 0
630
- volume_24h = float(vol) if vol else 0
631
- if dollar_volume >= 50000 and current_price > 0:
632
- volume_data.append({
633
- 'symbol': formatted_symbol, 'dollar_volume': dollar_volume,
634
- 'current_price': current_price, 'volume_24h': volume_24h,
635
- 'price_change_24h': price_change
636
- })
637
- except (ValueError, TypeError, KeyError) as e: continue
638
- print(f"✅ تم معالجة {len(volume_data)} عملة في الطريقة المباشرة (لجلب الحجم)")
639
  return volume_data
640
- except Exception as e:
641
- print(f"❌ خطأ في جلب بيانات الحجم المباشر: {e}")
642
- return []
643
-
644
  async def stream_ohlcv_data(self, symbols: List[Dict[str, Any]], queue: asyncio.Queue):
645
- """
646
- (محدث V7.2)
647
- جلب بيانات OHLCV كاملة (6 أطر زمنية) للعملات الناجحة فقط
648
- """
649
- print(f"📊 بدء تدفق بيانات OHLCV (الكاملة) لـ {len(symbols)} عملة (الناجحين من الغربلة)...")
650
-
651
- batch_size = 15
652
- batches = [symbols[i:i + batch_size] for i in range(0, len(symbols), batch_size)]
653
-
654
- total_successful = 0
655
-
656
  for batch_num, batch in enumerate(batches):
657
  print(f" 🔄 [المنتج] جلب الدفعة {batch_num + 1}/{len(batches)} ({len(batch)} عملة)...")
658
-
659
- batch_tasks = []
660
-
661
- # (V7.2 FIX)
662
- for symbol_data in batch:
663
- symbol_str = symbol_data['symbol']
664
- task = asyncio.create_task(self._fetch_complete_ohlcv_parallel(symbol_str))
665
- batch_tasks.append(task)
666
-
667
  batch_results = await asyncio.gather(*batch_tasks, return_exceptions=True)
668
-
669
  successful_data_for_batch = []
670
- successful_count = 0
671
  for i, result in enumerate(batch_results):
672
-
673
- original_symbol_data = batch[i]
674
- symbol_str = original_symbol_data['symbol']
675
-
676
- if isinstance(result, Exception):
677
- print(f" ❌ [المنتج] فشل جلب {symbol_str}: {result}")
678
  elif result is not None:
679
- result.update(original_symbol_data)
680
- successful_data_for_batch.append(result)
681
- successful_count += 1
682
- timeframes_count = result.get('successful_timeframes', 0)
683
- print(f" ✅ [المنتج] {symbol_str}: {timeframes_count}/6 أطر زمنية")
684
- else:
685
- print(f" ⚠️ [المنتج] {symbol_str}: بيانات غير كافية، تم التجاهل")
686
-
687
- print(f" 📦 [المنتج] اكتملت الدفعة {batch_num + 1}: {successful_count}/{len(batch)} ناجحة")
688
-
689
  if successful_data_for_batch:
690
- try:
691
- await queue.put(successful_data_for_batch)
692
- print(f" 📬 [المنتج] تم إرسال {len(successful_data_for_batch)} عملة إلى طابور المعالجة")
693
- total_successful += len(successful_data_for_batch)
694
- except Exception as q_err:
695
- print(f" ❌ [المنتج] فشل إرسال الدفعة للطابور: {q_err}")
696
-
697
- if batch_num < len(batches) - 1:
698
- await asyncio.sleep(1)
699
-
700
- print(f"✅ [المنتج] اكتمل تدفق بيانات OHLCV (الكاملة). تم إرسال {total_successful} عملة للمعالجة.")
701
-
702
- try:
703
- await queue.put(None)
704
- print(" 📬 [المنتج] تم إرسال إشارة الإنهاء (None) إلى الطابور.")
705
- except Exception as q_err:
706
- print(f" ❌ [المنتج] فشل إرسال إشارة الإنهاء (None) للطابور.")
707
-
708
 
709
  async def _fetch_complete_ohlcv_parallel(self, symbol: str) -> Dict[str, Any]:
710
- """(V7.2) جلب بيانات OHLCV كاملة - يتوقع 'symbol' كنص"""
711
  try:
712
- ohlcv_data = {}
713
-
714
- timeframes = [
715
- ('5m', 200), ('15m', 200), ('1h', 200),
716
- ('4h', 200), ('1d', 200), ('1w', 200),
717
- ]
718
-
719
- timeframe_tasks = []
720
- for timeframe, limit in timeframes:
721
- task = asyncio.create_task(self._fetch_single_timeframe_improved(symbol, timeframe, limit))
722
- timeframe_tasks.append(task)
723
-
724
- timeframe_results = await asyncio.gather(*timeframe_tasks, return_exceptions=True)
725
-
726
- successful_timeframes = 0
727
- min_required_timeframes = 2
728
-
729
  for i, (timeframe, limit) in enumerate(timeframes):
730
  result = timeframe_results[i]
731
  if isinstance(result, Exception): continue
732
- # (V8-MODIFICATION) زيادة الحد الأدنى من الشموع لاستيعاب المؤشرات
733
- if result and len(result) >= 200: # (كان 10)
734
- ohlcv_data[timeframe] = result
735
- successful_timeframes += 1
736
-
737
- # (V8-MODIFICATION) زيادة الحد الأدنى للأطر الزمنية
738
- if successful_timeframes >= 3 and ohlcv_data: # (كان 2)
739
  try:
740
  current_price = await self.get_latest_price_async(symbol)
741
  if current_price is None:
742
- for timeframe_data in ohlcv_data.values():
743
- if timeframe_data and len(timeframe_data) > 0:
744
- last_candle = timeframe_data[-1]
745
- if len(last_candle) >= 5:
746
- current_price = last_candle[4]; break
747
  if current_price is None: return None
748
-
749
- result_data = {
750
- 'symbol': symbol, 'ohlcv': ohlcv_data, 'raw_ohlcv': ohlcv_data,
751
- 'current_price': current_price, 'timestamp': datetime.now().isoformat(),
752
- 'candles_count': {tf: len(data) for tf, data in ohlcv_data.items()},
753
- 'successful_timeframes': successful_timeframes
754
- }
755
- return result_data
756
- except Exception as price_error: return None
757
  else: return None
758
- except Exception as e: return None
759
-
760
  async def _fetch_single_timeframe_improved(self, symbol: str, timeframe: str, limit: int):
761
- """(V7.2) جلب بيانات إطار زمني واحد - يتوقع 'symbol' كنص"""
762
- max_retries = 3
763
- retry_delay = 2
764
  for attempt in range(max_retries):
765
  try:
766
  ohlcv_data = self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
767
- if ohlcv_data and len(ohlcv_data) > 0:
768
- return ohlcv_data
769
- else:
770
- return []
771
- except Exception as e:
772
- if attempt < max_retries - 1:
773
- await asyncio.sleep(retry_delay * (attempt + 1))
774
- else:
775
- return []
776
-
777
  async def get_latest_price_async(self, symbol):
778
- """(V7.2) جلب السعر الحالي - يتوقع 'symbol' كنص"""
779
  try:
780
  if not self.exchange: return None
781
- if not symbol or '/' not in symbol: return None
782
  ticker = self.exchange.fetch_ticker(symbol)
783
- if not ticker: return None
784
- current_price = ticker.get('last')
785
- if current_price is None: return None
786
- return float(current_price)
787
- except Exception as e: return None
788
-
789
  async def get_whale_data_for_symbol(self, symbol):
790
- try:
791
- if self.whale_monitor:
792
- whale_data = await self.whale_monitor.get_symbol_whale_activity(symbol)
793
- return whale_data
794
- else: return None
795
- except Exception as e: return None
796
-
797
  async def get_whale_trading_signal(self, symbol, whale_data, market_context):
798
- try:
799
- if self.whale_monitor:
800
- return await self.whale_monitor.generate_whale_trading_signal(symbol, whale_data, market_context)
801
- else:
802
- return {'action': 'HOLD', 'confidence': 0.3, 'reason': 'Whale monitor not available', 'source': 'whale_analysis'}
803
- except Exception as e:
804
- return {'action': 'HOLD', 'confidence': 0.3, 'reason': f'Error: {str(e)}', 'source': 'whale_analysis'}
805
 
806
- print("✅ DataManager loaded - V7.9 (Re-balanced Spark Weights)")
 
1
+ # data_manager.py (Updated to V9.3 - High Confidence ML Ranker)
2
  import os
3
  import asyncio
4
  import httpx
 
14
  try:
15
  import pandas_ta as ta
16
  except ImportError:
17
+ print("⚠️ مكتبة pandas_ta غير موجودة. النظام سيفشل.")
18
  ta = None
19
 
20
+ # (V9.1) استيراد العقل الحسابي (الذي يحتوي على V9.1 Features)
21
  from ml_engine.indicators import AdvancedTechnicalAnalyzer
22
  from ml_engine.monte_carlo import MonteCarloAnalyzer
 
23
  from ml_engine.patterns import ChartPatternAnalyzer
24
+ # (V9.1) استيراد "العقل الذكي" الجديد
25
+ from ml_engine.ranker import Layer1Ranker
26
 
27
  logging.getLogger("httpx").setLevel(logging.WARNING)
28
  logging.getLogger("httpcore").setLevel(logging.WARNING)
29
 
30
  class DataManager:
 
31
  def __init__(self, contracts_db, whale_monitor, r2_service=None):
32
  self.contracts_db = contracts_db or {}
33
  self.whale_monitor = whale_monitor
34
+ self.r2_service = r2_service
35
 
36
  try:
37
  self.exchange = ccxt.kucoin({
38
+ 'sandbox': False, 'enableRateLimit': True,
39
+ 'timeout': 30000, 'verbose': False,
 
 
40
  })
41
  print("✅ تم تهيئة اتصال KuCoin بنجاح")
42
  except Exception as e:
 
47
  self.market_cache = {}
48
  self.last_market_load = None
49
 
50
+ # (V9.1) استيراد العقل الحسابي (الذي يحتوي على V9.1 Features)
51
  self.technical_analyzer = AdvancedTechnicalAnalyzer()
52
  self.monte_carlo_analyzer = MonteCarloAnalyzer()
53
+ self.pattern_analyzer = None
54
+ # (V9.1) تهيئة "العقل الذكي" (النموذج)
55
+ self.layer1_ranker = None
56
 
57
  async def initialize(self):
58
  self.http_client = httpx.AsyncClient(timeout=30.0)
59
  await self._load_markets()
60
 
 
61
  print(" > [DataManager] تهيئة محرك الأنماط V8 (ML-Based)...")
62
  try:
63
  self.pattern_analyzer = ChartPatternAnalyzer(r2_service=self.r2_service)
64
+ await self.pattern_analyzer.initialize()
65
  except Exception as e:
66
  print(f"❌ [DataManager] فشل تهيئة محرك الأنماط V8: {e}")
 
67
  self.pattern_analyzer = ChartPatternAnalyzer(r2_service=None)
68
+
69
+ # 🔴 --- START OF CHANGE (V9.1) --- 🔴
70
+ # (تهيئة "العقل الذكي" - نموذج الرانكر V9.1)
71
+ print(" > [DataManager] تهيئة الكاشف المصغر (Layer1 Ranker V9.1)...")
72
+ try:
73
+ # (نتوقع أن يكون النموذج موجوداً في هذا المسار)
74
+ model_file_path = "ml_models/layer1_ranker.lgbm"
75
+ self.layer1_ranker = Layer1Ranker(model_path=model_file_path)
76
+ await self.layer1_ranker.initialize()
77
+ if self.layer1_ranker.model is None:
78
+ print(" ⚠️ [DataManager V9.1] الرانكر في وضع 'وهمي' (Placeholder).")
79
+ else:
80
+ print(f" ✅ [DataManager V9.1] الرانكر {self.layer1_ranker.model_name} جاهز للعمل.")
81
+ except Exception as e:
82
+ print(f"❌ [DataManager V9.1] فشل تهيئة الرانكر V9.1: {e}")
83
+ self.layer1_ranker = None
84
+ # 🔴 --- END OF CHANGE (V9.1) --- 🔴
85
 
86
+ print("✅ DataManager initialized - V9.3 (High Confidence Ranker)")
87
 
88
  async def _load_markets(self):
89
  try:
90
+ if not self.exchange: return
 
 
91
  print("🔄 جلب أحدث بيانات الأسواق من KuCoin...")
92
  self.exchange.load_markets()
93
  self.market_cache = self.exchange.markets
94
  self.last_market_load = datetime.now()
95
  print(f"✅ تم تحميل {len(self.market_cache)} سوق من KuCoin")
96
+ except Exception as e: print(f"❌ فشل تحميل بيانات الأسواق: {e}")
 
 
97
 
98
  async def close(self):
99
+ if self.http_client and not self.http_client.is_closed: await self.http_client.aclose()
 
 
 
100
  if self.exchange:
101
+ try: await self.exchange.close()
102
+ except Exception: pass
103
+
 
 
 
104
  async def get_market_context_async(self):
105
  try:
106
  sentiment_data = await self.get_sentiment_safe_async()
107
  price_data = await self._get_prices_with_fallback()
108
+ bitcoin_price = price_data.get('bitcoin'); ethereum_price = price_data.get('ethereum')
109
+ return {
 
 
 
110
  'timestamp': datetime.now().isoformat(),
111
+ 'bitcoin_price_usd': bitcoin_price, 'ethereum_price_usd': ethereum_price,
 
112
  'fear_and_greed_index': sentiment_data.get('feargreed_value') if sentiment_data else None,
113
  'sentiment_class': sentiment_data.get('feargreed_class') if sentiment_data else 'NEUTRAL',
114
  'market_trend': self._determine_market_trend(bitcoin_price, sentiment_data),
115
  'btc_sentiment': self._get_btc_sentiment(bitcoin_price),
116
  'data_quality': 'HIGH' if bitcoin_price and ethereum_price else 'LOW'
117
  }
118
+ except Exception as e: return self._get_minimal_market_context()
 
 
 
 
119
 
120
+ # ... (دوال get_sentiment_safe_async, _determine_market_trend... إلخ - لا تغيير) ...
121
  async def get_sentiment_safe_async(self):
122
  try:
123
  async with httpx.AsyncClient(timeout=10) as client:
124
  response = await client.get("https://api.alternative.me/fng/")
125
+ response.raise_for_status(); data = response.json()
126
+ if 'data' not in data or not data['data']: raise ValueError("بيانات المشاعر غير متوفرة")
 
 
 
 
127
  latest_data = data['data'][0]
128
+ return { "feargreed_value": int(latest_data['value']), "feargreed_class": latest_data['value_classification'], "source": "alternative.me", "timestamp": datetime.now().isoformat() }
129
+ except Exception as e: return None
 
 
 
 
 
 
 
130
  def _determine_market_trend(self, bitcoin_price, sentiment_data):
131
+ if bitcoin_price is None: return "UNKNOWN"
 
132
  if bitcoin_price > 60000: score = 1
133
  elif bitcoin_price < 55000: score = -1
134
  else: score = 0
 
139
  if score >= 1: return "bull_market"
140
  elif score <= -1: return "bear_market"
141
  else: return "sideways_market"
 
142
  def _get_btc_sentiment(self, bitcoin_price):
143
  if bitcoin_price is None: return 'UNKNOWN'
144
  elif bitcoin_price > 60000: return 'BULLISH'
145
  elif bitcoin_price < 55000: return 'BEARISH'
146
  else: return 'NEUTRAL'
 
147
  async def _get_prices_with_fallback(self):
148
  try:
149
  prices = await self._get_prices_from_kucoin_safe()
150
+ if prices.get('bitcoin') and prices.get('ethereum'): return prices
 
151
  return await self._get_prices_from_coingecko()
152
+ except Exception as e: return {'bitcoin': None, 'ethereum': None}
 
 
153
  async def _get_prices_from_kucoin_safe(self):
154
  if not self.exchange: return {'bitcoin': None, 'ethereum': None}
155
  try:
156
  prices = {'bitcoin': None, 'ethereum': None}
157
+ btc_ticker = self.exchange.fetch_ticker('BTC/USDT'); btc_price = float(btc_ticker.get('last', 0)) if btc_ticker.get('last') else None
 
158
  if btc_price and btc_price > 0: prices['bitcoin'] = btc_price
159
+ eth_ticker = self.exchange.fetch_ticker('ETH/USDT'); eth_price = float(eth_ticker.get('last', 0)) if eth_ticker.get('last') else None
 
160
  if eth_price and eth_price > 0: prices['ethereum'] = eth_price
161
  return prices
162
+ except Exception as e: return {'bitcoin': None, 'ethereum': None}
 
 
163
  async def _get_prices_from_coingecko(self):
164
  try:
165
  await asyncio.sleep(0.5)
 
167
  headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'application/json'}
168
  async with httpx.AsyncClient(headers=headers) as client:
169
  response = await client.get(url, timeout=10)
170
+ if response.status_code == 429: await asyncio.sleep(2); response = await client.get(url, timeout=10)
171
+ response.raise_for_status(); data = response.json()
172
+ btc_price = data.get('bitcoin', {}).get('usd'); eth_price = data.get('ethereum', {}).get('usd')
173
+ if btc_price and eth_price: return {'bitcoin': btc_price, 'ethereum': eth_price}
174
+ else: return {'bitcoin': None, 'ethereum': None}
175
+ except Exception as e: return {'bitcoin': None, 'ethereum': None}
 
 
 
 
 
 
 
 
176
  def _get_minimal_market_context(self):
177
+ return { 'timestamp': datetime.now().isoformat(), 'data_available': False, 'market_trend': 'UNKNOWN', 'btc_sentiment': 'UNKNOWN', 'data_quality': 'LOW' }
 
 
 
 
 
 
178
 
 
179
  def _create_dataframe(self, candles: List) -> pd.DataFrame:
180
+ """(V9.1) إنشاء DataFrame (تحتاج 200 شمعة للميزات)"""
181
  try:
182
  if not candles: return pd.DataFrame()
183
  df = pd.DataFrame(candles, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
 
187
  df.sort_index(inplace=True)
188
  return df
189
  except Exception as e:
190
+ print(f"❌ خطأ في إنشاء DataFrame: {e}")
191
  return pd.DataFrame()
192
 
193
+ # 🔴 --- (تم حذف جميع الفلاتر الغبية V7.x نهائياً) --- 🔴
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
 
195
+ # 🔴 --- START OF CHANGE (V9.3 - High Confidence ML Ranker) --- 🔴
196
  async def layer1_rapid_screening(self) -> List[Dict[str, Any]]:
197
  """
198
+ الطبقة 1: فحص سريع - (محدث V9.3)
199
+ - يستخدم "العقل الذكي" (V9.2 Model).
200
+ - يطبق عتبة ثقة صارمة (70%) لضمان الجودة.
201
  """
202
+ print("📊 الطبقة 1 (V9.3 - High Confidence Ranker): بدء الغربلة...")
203
 
204
+ if not self.layer1_ranker:
205
+ print("❌ [V9.3] الرانكر غير مهيأ. إيقاف الغربلة.")
206
+ return []
207
+
208
  # الخطوة 1: جلب أفضل 100 عملة حسب الحجم
209
  volume_data = await self._get_volume_data_optimal()
210
+ if not volume_data: volume_data = await self._get_volume_data_direct_api()
211
  if not volume_data:
212
+ print("❌ [V9.3] فشل جلب بيانات الأحجام.")
 
 
 
213
  return []
214
 
215
  volume_data.sort(key=lambda x: x['dollar_volume'], reverse=True)
216
  top_100_by_volume = volume_data[:100]
217
+ print(f"✅ [V9.3] تم تحديد أفضل {len(top_100_by_volume)} عملة. بدء حساب الميزات الذكية...")
218
 
219
+ final_candidates_with_scores = []
220
+ batch_symbols_data = top_100_by_volume
221
+ batch_symbols = [s['symbol'] for s in batch_symbols_data]
 
 
 
 
 
 
 
 
 
 
 
222
 
223
+ # الخطوة 2: جلب بيانات 1H (200 شمعة)
224
+ tasks = [self._fetch_1h_ohlcv_for_screening(symbol, limit=200) for symbol in batch_symbols]
225
+ results_candles = await asyncio.gather(*tasks, return_exceptions=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
 
227
+ valid_symbol_data_for_ranking = []
228
+ for j, (candles) in enumerate(results_candles):
229
+ symbol_data = batch_symbols_data[j]
230
+ if isinstance(candles, Exception) or not candles or len(candles) < 200: continue
231
+ symbol_data['ohlcv_1h_raw'] = candles
232
+ valid_symbol_data_for_ranking.append(symbol_data)
233
+
234
+ if not valid_symbol_data_for_ranking:
235
+ print("❌ [V9.3] لا توجد عملات صالحة (تحتاج 200 شمعة 1H).")
236
+ return []
 
 
 
 
 
 
 
237
 
238
+ print(f" 🔄 [V9.3] حساب الميزات الذكية لـ {len(valid_symbol_data_for_ranking)} عملة...")
239
 
240
+ # الخطوة 3: حساب "الميزات الذكية V9.1"
241
+ all_features_list = []
242
+ symbols_in_order = []
243
+ for symbol_data in valid_symbol_data_for_ranking:
244
+ try:
245
+ df = self._create_dataframe(symbol_data['ohlcv_1h_raw'])
246
+ if df.empty: continue
247
+ smart_features = self.technical_analyzer.calculate_v9_smart_features(df)
248
+ if smart_features:
249
+ all_features_list.append(smart_features)
250
+ symbols_in_order.append(symbol_data)
251
+ except Exception: pass
252
 
253
+ if not all_features_list:
254
+ print("❌ [V9.3] فشل حساب الميزات الذكية.")
255
+ return []
256
+
257
+ # الخطوة 4: التنبؤ (التصنيف)
258
+ print(f" 🧠 [V9.3] إرسال {len(all_features_list)} عملة إلى نموذج الرانكر...")
259
+ features_dataframe = pd.DataFrame(all_features_list)
260
+ probabilities = self.layer1_ranker.predict_proba(features_dataframe)
261
 
262
+ # الخطوة 5: تجميع النتائج (مع عتبة الثقة العالية 70%)
263
+ for i, (symbol_data) in enumerate(symbols_in_order):
264
+ score = probabilities[i]
 
265
 
266
+ # 🔴 (V9.3: عتبة الثقة الصارمة - طلب المستخدم) 🔴
267
+ if score >= 0.70:
268
+ symbol = symbol_data['symbol']
269
+ print(f" ✅ {symbol}: نجح بامتياز (الاحتمالية: {score:.3f})")
270
+ symbol_data['layer1_score'] = float(score)
271
+ symbol_data['reasons_for_candidacy'] = ["V9_SMART_RANKER_HIGH_CONFIDENCE"]
272
+ if 'ohlcv_1h_raw' in symbol_data: del symbol_data['ohlcv_1h_raw']
273
+ final_candidates_with_scores.append(symbol_data)
274
 
275
+ print(f"🎯 اكتملت الغربلة (V9.3). تم تأهيل {len(final_candidates_with_scores)} عملة (ثقة >= 70%).")
 
 
 
 
 
 
 
276
 
277
+ if final_candidates_with_scores:
278
+ final_candidates_with_scores.sort(key=lambda x: x['layer1_score'], reverse=True)
279
+ print("🏆 المرشحون الناجحون (Top Candidates):")
280
+ for k, candidate in enumerate(final_candidates_with_scores[:5]):
281
+ print(f" {k+1}. {candidate['symbol']}: (Score: {candidate.get('layer1_score'):.3f})")
282
  else:
283
+ print("⚠️ [V9.3] لم تنجح أي عملة في تجاوز عتبة الثقة 70%. (هذا جيد، النظام حذر)")
 
 
 
284
 
285
+ return final_candidates_with_scores[:20]
286
+ # 🔴 --- END OF CHANGE (V9.3) --- 🔴
287
 
288
+ async def _fetch_1h_ohlcv_for_screening(self, symbol: str, limit: int = 200) -> List:
 
289
  try:
290
+ ohlcv_data = self.exchange.fetch_ohlcv(symbol, '1h', limit=limit)
291
+ if not ohlcv_data or len(ohlcv_data) < limit: return None
 
 
292
  return ohlcv_data
293
+ except Exception: return None
 
294
 
295
+ # ... (باقي الدوال _get_volume_data_optimal, stream_ohlcv_data... إلخ - لا تغيير) ...
296
  async def _get_volume_data_optimal(self) -> List[Dict[str, Any]]:
297
  try:
298
  if not self.exchange: return []
299
+ tickers = self.exchange.fetch_tickers(); volume_data = []
 
300
  for symbol, ticker in tickers.items():
301
  if not symbol.endswith('/USDT') or not ticker.get('active', True): continue
302
+ current_price = ticker.get('last', 0); quote_volume = ticker.get('quoteVolume', 0)
 
303
  if current_price is None or current_price <= 0: continue
304
+ if quote_volume is not None and quote_volume > 0: dollar_volume = quote_volume
 
305
  else:
306
  base_volume = ticker.get('baseVolume', 0)
307
  if base_volume is None: continue
308
  dollar_volume = base_volume * current_price
309
  if dollar_volume is None or dollar_volume < 50000: continue
310
+ price_change_24h = ticker.get('percentage', 0) or 0; if price_change_24h is None: price_change_24h = 0
311
+ volume_data.append({ 'symbol': symbol, 'dollar_volume': dollar_volume, 'current_price': current_price, 'volume_24h': ticker.get('baseVolume', 0) or 0, 'price_change_24h': price_change_24h })
312
+ print(f"✅ تم معالجة {len(volume_data)} عملة في الطريقة المثلى")
 
 
 
 
 
 
 
313
  return volume_data
314
+ except Exception as e: print(f"❌ خطأ في جلب بيانات الحجم المثلى: {e}"); return []
 
 
 
315
  async def _get_volume_data_direct_api(self) -> List[Dict[str, Any]]:
316
  try:
317
  url = "https://api.kucoin.com/api/v1/market/allTickers"
318
  async with httpx.AsyncClient(timeout=15) as client:
319
+ response = await client.get(url); response.raise_for_status(); data = response.json()
 
 
320
  if data.get('code') != '200000': raise ValueError(f"استجابة API غير متوقعة: {data.get('code')}")
321
+ tickers = data['data']['ticker']; volume_data = []
 
322
  for ticker in tickers:
323
  symbol = ticker['symbol']
324
  if not symbol.endswith('USDT'): continue
325
  formatted_symbol = symbol.replace('-', '/')
326
  try:
327
+ vol_value = ticker.get('volValue'); last_price = ticker.get('last'); change_rate = ticker.get('changeRate'); vol = ticker.get('vol')
 
 
 
328
  if vol_value is None or last_price is None or change_rate is None or vol is None: continue
329
+ dollar_volume = float(vol_value) if vol_value else 0; current_price = float(last_price) if last_price else 0; price_change = (float(change_rate) * 100) if change_rate else 0; volume_24h = float(vol) if vol else 0
330
+ if dollar_volume >= 50000 and current_price > 0: volume_data.append({ 'symbol': formatted_symbol, 'dollar_volume': dollar_volume, 'current_price': current_price, 'volume_24h': volume_24h, 'price_change_24h': price_change })
331
+ except (ValueError, TypeError, KeyError): continue
332
+ print(f"✅ تم معالجة {len(volume_data)} عملة في الطريقة المباشرة")
 
 
 
 
 
 
 
 
333
  return volume_data
334
+ except Exception as e: print(f"❌ خطأ ف�� جلب بيانات الحجم المباشر: {e}"); return []
 
 
 
335
  async def stream_ohlcv_data(self, symbols: List[Dict[str, Any]], queue: asyncio.Queue):
336
+ print(f"📊 بدء تدفق بيانات OHLCV (الكاملة) لـ {len(symbols)} عملة (مصنفة)...")
337
+ batch_size = 15; batches = [symbols[i:i + batch_size] for i in range(0, len(symbols), batch_size)]; total_successful = 0
 
 
 
 
 
 
 
 
 
338
  for batch_num, batch in enumerate(batches):
339
  print(f" 🔄 [المنتج] جلب الدفعة {batch_num + 1}/{len(batches)} ({len(batch)} عملة)...")
340
+ batch_tasks = []; for symbol_data in batch: task = asyncio.create_task(self._fetch_complete_ohlcv_parallel(symbol_data['symbol'])); batch_tasks.append(task)
 
 
 
 
 
 
 
 
341
  batch_results = await asyncio.gather(*batch_tasks, return_exceptions=True)
 
342
  successful_data_for_batch = []
 
343
  for i, result in enumerate(batch_results):
344
+ if isinstance(result, Exception): print(f" ❌ [المنتج] فشل جلب {batch[i]['symbol']}: {result}")
 
 
 
 
 
345
  elif result is not None:
346
+ result.update(batch[i]); successful_data_for_batch.append(result)
347
+ print(f" ✅ [المنتج] {batch[i]['symbol']}: {result.get('successful_timeframes', 0)}/6 أطر زمنية")
 
 
 
 
 
 
 
 
348
  if successful_data_for_batch:
349
+ try: await queue.put(successful_data_for_batch); total_successful += len(successful_data_for_batch)
350
+ except Exception as q_err: print(f" ❌ [المنتج] فشل إرسال الدفعة للطابور: {q_err}")
351
+ if batch_num < len(batches) - 1: await asyncio.sleep(1)
352
+ print(f"✅ [المنتج] اكتمل التدفق. تم إرسال {total_successful} عملة."); await queue.put(None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
 
354
  async def _fetch_complete_ohlcv_parallel(self, symbol: str) -> Dict[str, Any]:
 
355
  try:
356
+ ohlcv_data = {}; timeframes = [('5m', 200), ('15m', 200), ('1h', 200), ('4h', 200), ('1d', 200), ('1w', 200)]; timeframe_tasks = []
357
+ for timeframe, limit in timeframes: task = asyncio.create_task(self._fetch_single_timeframe_improved(symbol, timeframe, limit)); timeframe_tasks.append(task)
358
+ timeframe_results = await asyncio.gather(*timeframe_tasks, return_exceptions=True); successful_timeframes = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
  for i, (timeframe, limit) in enumerate(timeframes):
360
  result = timeframe_results[i]
361
  if isinstance(result, Exception): continue
362
+ if result and len(result) >= 200: ohlcv_data[timeframe] = result; successful_timeframes += 1
363
+ if successful_timeframes >= 3 and ohlcv_data:
 
 
 
 
 
364
  try:
365
  current_price = await self.get_latest_price_async(symbol)
366
  if current_price is None:
367
+ for tf_data in ohlcv_data.values():
368
+ if tf_data and len(tf_data) > 0: current_price = tf_data[-1][4]; break
 
 
 
369
  if current_price is None: return None
370
+ return { 'symbol': symbol, 'ohlcv': ohlcv_data, 'raw_ohlcv': ohlcv_data, 'current_price': current_price, 'timestamp': datetime.now().isoformat(), 'candles_count': {tf: len(data) for tf, data in ohlcv_data.items()}, 'successful_timeframes': successful_timeframes }
371
+ except Exception: return None
 
 
 
 
 
 
 
372
  else: return None
373
+ except Exception: return None
 
374
  async def _fetch_single_timeframe_improved(self, symbol: str, timeframe: str, limit: int):
375
+ max_retries = 3; retry_delay = 2
 
 
376
  for attempt in range(max_retries):
377
  try:
378
  ohlcv_data = self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
379
+ if ohlcv_data and len(ohlcv_data) > 0: return ohlcv_data
380
+ else: return []
381
+ except Exception:
382
+ if attempt < max_retries - 1: await asyncio.sleep(retry_delay * (attempt + 1))
383
+ else: return []
 
 
 
 
 
384
  async def get_latest_price_async(self, symbol):
 
385
  try:
386
  if not self.exchange: return None
 
387
  ticker = self.exchange.fetch_ticker(symbol)
388
+ return float(ticker['last']) if ticker and ticker.get('last') else None
389
+ except Exception: return None
 
 
 
 
390
  async def get_whale_data_for_symbol(self, symbol):
391
+ try: return await self.whale_monitor.get_symbol_whale_activity(symbol) if self.whale_monitor else None
392
+ except Exception: return None
 
 
 
 
 
393
  async def get_whale_trading_signal(self, symbol, whale_data, market_context):
394
+ try: return await self.whale_monitor.generate_whale_trading_signal(symbol, whale_data, market_context) if self.whale_monitor else {'action': 'HOLD', 'confidence': 0.3, 'reason': 'Whale monitor not available', 'source': 'whale_analysis'}
395
+ except Exception as e: return {'action': 'HOLD', 'confidence': 0.3, 'reason': f'Error: {str(e)}', 'source': 'whale_analysis'}
 
 
 
 
 
396
 
397
+ print("✅ DataManager loaded - V9.3 (High Confidence Ranker)")