Riy777 commited on
Commit
da260b6
·
1 Parent(s): 21e1cbd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +766 -556
app.py CHANGED
@@ -1,621 +1,831 @@
1
- # app.py (محدث)
2
  import os
3
- import traceback
4
- import signal
5
- import sys
6
- import uvicorn
7
  import asyncio
8
- import json
 
9
  import time
10
- from contextlib import asynccontextmanager
11
- from fastapi import FastAPI, HTTPException
12
  from datetime import datetime
13
- from r2 import R2Service
14
- from LLM import LLMService
15
- from data_manager import DataManager
16
- from ML import MLProcessor
17
- from learning_engine import LearningEngine
18
- from sentiment_news import SentimentAnalyzer
19
- from trade_manager import TradeManager
20
- import state
21
- from helpers import safe_float_conversion, validate_candidate_data_enhanced
22
-
23
- # المتغيرات العالمية
24
- r2_service_global = None
25
- data_manager_global = None
26
- llm_service_global = None
27
- learning_engine_global = None
28
- trade_manager_global = None
29
- sentiment_analyzer_global = None
30
- symbol_whale_monitor_global = None
31
-
32
- class StateManager:
33
- def __init__(self):
34
- self.market_analysis_lock = asyncio.Lock()
35
- self.trade_analysis_lock = asyncio.Lock()
36
- self.initialization_complete = False
37
- self.services_initialized = {
38
- 'r2_service': False,
39
- 'data_manager': False,
40
- 'llm_service': False,
41
- 'learning_engine': False,
42
- 'trade_manager': False,
43
- 'sentiment_analyzer': False,
44
- 'symbol_whale_monitor': False
45
- }
46
 
47
- async def wait_for_initialization(self, timeout=30):
48
- start_time = time.time()
49
- while not self.initialization_complete and (time.time() - start_time) < timeout:
50
- await asyncio.sleep(1)
51
- return self.initialization_complete
52
 
53
- def set_service_initialized(self, service_name):
54
- self.services_initialized[service_name] = True
55
- if all(self.services_initialized.values()):
56
- self.initialization_complete = True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
- state_manager = StateManager()
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
- async def monitor_market_async():
61
- """مراقبة السوق"""
62
- global data_manager_global, sentiment_analyzer_global
63
-
64
- if not await state_manager.wait_for_initialization():
65
- print(" فشل تهيئة الخدمات - إوقف مراقبة السوق")
66
- return
67
-
68
- while True:
69
  try:
70
- async with state_manager.market_analysis_lock:
71
- market_context = await sentiment_analyzer_global.get_market_sentiment()
72
- if not market_context:
73
- state.MARKET_STATE_OK = True
74
- await asyncio.sleep(60)
75
- continue
76
-
77
- bitcoin_sentiment = market_context.get('btc_sentiment')
78
- fear_greed_index = market_context.get('fear_and_greed_index')
 
 
 
 
 
 
 
 
 
79
 
80
- should_halt_trading, halt_reason = False, ""
 
 
 
 
 
 
 
 
 
 
81
 
82
- if bitcoin_sentiment == 'BEARISH' and (fear_greed_index is not None and fear_greed_index < 30):
83
- should_halt_trading, halt_reason = True, "ظروف سوق هابطة"
84
-
85
- if should_halt_trading:
86
- state.MARKET_STATE_OK = False
87
- await r2_service_global.save_system_logs_async({"market_halt": True, "reason": halt_reason})
88
- else:
89
- if not state.MARKET_STATE_OK:
90
- print(" تحسنت ظروف السوق. استئناف العمليات العادية.")
91
- state.MARKET_STATE_OK = True
92
-
93
- await asyncio.sleep(60)
94
- except Exception as error:
95
- print(f"❌ خطأ أثناء مراقبة السوق: {error}")
96
- state.MARKET_STATE_OK = True
97
- await asyncio.sleep(60)
98
-
99
- async def process_batch_parallel(batch, ml_processor, batch_num, total_batches):
100
- """معالجة دفعة من الرموز بشكل متوازي"""
101
- try:
102
- print(f" 🔄 معالجة الدفعة {batch_num}/{total_batches} ({len(batch)} عملة)...")
103
-
104
- # إنشاء مهام للدفعة الحالية
105
- batch_tasks = []
106
- for symbol_data in batch:
107
- task = asyncio.create_task(ml_processor.process_and_score_symbol_enhanced(symbol_data))
108
- batch_tasks.append(task)
109
-
110
- # انتظار انتهاء جميع مهام الدفعة الحالية
111
- batch_results = await asyncio.gather(*batch_tasks, return_exceptions=True)
112
-
113
- # تصفية النتائج الناجحة
114
- successful_results = []
115
- for result in batch_results:
116
- if isinstance(result, Exception):
117
- continue
118
- if result and result.get('enhanced_final_score', 0) > 0.4:
119
- successful_results.append(result)
120
-
121
- print(f" ✅ اكتملت الدفعة {batch_num}: {len(successful_results)}/{len(batch)} ناجحة")
122
- return successful_results
123
-
124
- except Exception as error:
125
- print(f"❌ خطأ في معالجة الدفعة {batch_num}: {error}")
126
- return []
127
-
128
- async def run_3_layer_analysis():
129
- """
130
- تشغيل النظام الطبقي المكون من 3 طبقات:
131
- الطبقة 1: data_manager - الفحص السريع
132
- الطبقة 2: MLProcessor - التحليل المتقدم
133
- الطبقة 3: LLMService - النموذج الضخم
134
- """
135
- try:
136
- print("🎯 بدء النظام الطبقي المكون من 3 طبقات...")
137
-
138
- if not await state_manager.wait_for_initialization():
139
- print("❌ الخدمات غير مهيأة بالكامل")
140
  return None
141
 
142
- # الطبقة 1: الفحص السريع لجميع العملات
143
- print("\n🔍 الطبقة 1: الفحص السريع (data_manager)...")
144
- layer1_candidates = await data_manager_global.layer1_rapid_screening()
145
-
146
- if not layer1_candidates:
147
- print("❌ لم يتم العثور على مرشحين في الطبقة 1")
148
- return None
149
-
150
- print(f"✅ تم ��ختيار {len(layer1_candidates)} عملة للطبقة 2")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
- # جلب بيانات OHLCV كاملة للمرشحين
153
- layer1_symbols = [candidate['symbol'] for candidate in layer1_candidates]
154
- ohlcv_data_list = await data_manager_global.get_ohlcv_data_for_symbols(layer1_symbols)
155
-
156
- if not ohlcv_data_list:
157
- print("❌ فشل جلب بيانات OHLCV للمرشحين")
158
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
- print(f"📊 تم جلب بيانات OHLCV لـ {len(ohlcv_data_list)} عملة بنجاح")
 
 
 
 
 
 
 
 
161
 
162
- # الطبقة 2: التحليل المتقدم بشكل متوازي حقيقي
163
- print(f"\n📈 الطبقة 2: التحليل المتقدم (MLProcessor) بشكل متوازي لـ {len(ohlcv_data_list)} عملة...")
164
- market_context = await data_manager_global.get_market_context_async()
 
 
165
 
166
- # إنشاء معالج ML
167
- ml_processor = MLProcessor(market_context, data_manager_global, learning_engine_global)
 
168
 
169
- # تجهيز البيانات للطبقة 2
170
- layer2_data = []
171
- for ohlcv_data in ohlcv_data_list:
172
- try:
173
- # إضافة أسباب الترشيح من الطبقة 1
174
- symbol = ohlcv_data['symbol']
175
- layer1_candidate = next((c for c in layer1_candidates if c['symbol'] == symbol), None)
176
- if layer1_candidate:
177
- ohlcv_data['reasons_for_candidacy'] = layer1_candidate.get('reasons', [])
178
- ohlcv_data['layer1_score'] = layer1_candidate.get('layer1_score', 0)
179
- ohlcv_data['successful_timeframes'] = ohlcv_data.get('successful_timeframes', 0)
180
-
181
- layer2_data.append(ohlcv_data)
182
-
183
- except Exception as e:
184
- print(f"❌ خطأ في إعداد تحليل {ohlcv_data.get('symbol')}: {e}")
185
- continue
186
 
187
- if not layer2_data:
188
- print("❌ فشل إعداد بيانات الطبقة 2")
189
- return None
 
190
 
191
- # تقسيم العمل إلى دفعات للمعالجة المتوازية
192
- batch_size = 15 # تقليل حجم الدفعة
193
- batches = [layer2_data[i:i + batch_size] for i in range(0, len(layer2_data), batch_size)]
194
- total_batches = len(batches)
195
 
196
- print(f" 🚀 تقسيم العمل إلى {total_batches} دفعة ({batch_size} عملة لكل دفعة)...")
 
 
197
 
198
- # معالجة جميع الدفعات بشكل متوازي
199
- batch_tasks = []
200
- for i, batch in enumerate(batches):
201
- task = asyncio.create_task(process_batch_parallel(batch, ml_processor, i+1, total_batches))
202
- batch_tasks.append(task)
203
 
204
- # جمع نتائج جميع الدفعات
205
- batch_results = await asyncio.gather(*batch_tasks)
 
206
 
207
- # دمج جميع النتائج
208
- layer2_candidates = []
209
- for batch_result in batch_results:
210
- layer2_candidates.extend(batch_result)
211
 
212
- print(f"✅ اكتمل التحليل المتقدم: {len(layer2_candidates)}/{len(layer2_data)} عملة تم تحليلها بنجاح")
 
 
 
 
 
 
213
 
214
- if not layer2_candidates:
215
- print("❌ لم يتم العثور على مرشحين في الطبقة 2")
216
- return None
217
-
218
- # ترتيب المرشحين حسب الدرجة المحسنة وأخذ أقوى 10 مرشحين فقط للطبقة 3
219
- layer2_candidates.sort(key=lambda x: x.get('enhanced_final_score', 0), reverse=True)
220
- target_count = min(10, len(layer2_candidates))
221
- final_layer2_candidates = layer2_candidates[:target_count]
222
-
223
- print(f"🎯 تم اختيار {len(final_layer2_candidates)} عملة للطبقة 3 (الأقوى فقط)")
224
 
225
- # حفظ المرشحين العشرة في ملف Candidates في R2
226
- await r2_service_global.save_candidates_async(final_layer2_candidates)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
 
228
- # عرض أفضل 10 عملات من الطبقة 2
229
- print("\n🏆 أفضل 10 عملات من الطبقة 2:")
230
- for i, candidate in enumerate(final_layer2_candidates):
231
- score = candidate.get('enhanced_final_score', 0)
232
- strategy = candidate.get('target_strategy', 'GENERIC')
233
- mc_score = candidate.get('monte_carlo_probability', 0)
234
- pattern = candidate.get('pattern_analysis', {}).get('pattern_detected', 'no_pattern')
235
- timeframes = candidate.get('successful_timeframes', 0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
 
237
- print(f" {i+1}. {candidate['symbol']}:")
238
- print(f" 📊 النهائي: {score:.3f} | الأطر: {timeframes}/6")
239
- if mc_score > 0:
240
- print(f" 🎯 مونت كارلو: {mc_score:.3f}")
241
- print(f" 🎯 استراتيجية: {strategy} | نمط: {pattern}")
242
 
243
- # الطبقة 3: التحليل بالنموذج الضخم
244
- print("\n🧠 الطبقة 3: التحليل بالنموذج الضخم (LLMService)...")
245
- final_opportunities = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
 
247
- for candidate in final_layer2_candidates:
 
 
248
  try:
249
- symbol = candidate['symbol']
250
- print(f" 🤔 تحليل {symbol} بالنموذج الضخم...")
251
 
252
- # التأكد من تمرير بيانات OHLCV بشكل صحيح
253
- ohlcv_data = candidate.get('ohlcv') or candidate.get('raw_ohlcv')
254
- if not ohlcv_data:
255
- print(f" ⚠️ لا توجد بيانات شموع لـ {symbol}")
256
  continue
257
 
258
- # التأكد من تمرير البيانات الخام للنموذج
259
- candidate['raw_ohlcv'] = ohlcv_data
260
- candidate['ohlcv'] = ohlcv_data
261
-
262
- timeframes_count = candidate.get('successful_timeframes', 0)
263
- total_candles = sum(len(data) for data in ohlcv_data.values())
264
 
265
- print(f" 📊 بيانات الشموع: {timeframes_count} إطار زمني, {total_candles} شمعة")
 
 
266
 
267
- # ✅ إرسال كل عملة للنموذج الضخم على حدة
268
- llm_analysis = await llm_service_global.get_trading_decision(candidate)
269
 
270
- # التحقق من وجود قرار صالح من النموذج
271
- if llm_analysis and llm_analysis.get('action') in ['BUY', 'SELL']:
272
- opportunity = {
273
- 'symbol': symbol,
274
- 'current_price': candidate.get('current_price', 0),
275
- 'decision': llm_analysis,
276
- 'enhanced_score': candidate.get('enhanced_final_score', 0),
277
- 'llm_confidence': llm_analysis.get('confidence_level', 0),
278
- 'strategy': llm_analysis.get('strategy', 'GENERIC'),
279
- 'analysis_timestamp': datetime.now().isoformat(),
280
- 'timeframes_count': timeframes_count,
281
- 'total_candles': total_candles
282
- }
283
- final_opportunities.append(opportunity)
284
 
285
- print(f" ✅ {symbol}: {llm_analysis.get('action')} - ثقة: {llm_analysis.get('confidence_level', 0):.2f}")
286
- else:
287
- action = llm_analysis.get('action', 'NO_DECISION') if llm_analysis else 'NO_RESPONSE'
288
- print(f" ⚠️ {symbol}: لا يوجد قرار تداول من النموذج الضخم ({action})")
289
-
290
  except Exception as e:
291
- print(f"❌ خطأ في تحليل النموذج الضخم لـ {candidate.get('symbol')}: {e}")
292
  continue
293
 
294
- if not final_opportunities:
295
- print("❌ لم يتم العثور على فرص تداول مناسبة")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
  return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
 
298
- # ترتيب الفرص النهائية حسب الثقة والدرجة
299
- final_opportunities.sort(key=lambda x: (x['llm_confidence'] + x['enhanced_score']) / 2, reverse=True)
300
-
301
- print(f"\n🏆 النظام الطبقي اكتمل: {len(final_opportunities)} فرصة تداول")
302
- for i, opportunity in enumerate(final_opportunities[:5]):
303
- print(f" {i+1}. {opportunity['symbol']}: {opportunity['decision'].get('action')} - ثقة: {opportunity['llm_confidence']:.2f} - أطر: {opportunity['timeframes_count']}")
304
-
305
- return final_opportunities[0] if final_opportunities else None
306
 
307
- except Exception as error:
308
- print(f"❌ خطأ في النظام الطبقي: {error}")
309
- import traceback
310
- traceback.print_exc()
311
- return None
312
 
313
- async def re_analyze_open_trade_async(trade_data):
314
- """إعادة تحليل الصفقة المفتوحة"""
315
- symbol = trade_data.get('symbol')
316
- try:
317
- async with state_manager.trade_analysis_lock:
318
- # جلب البيانات الحالية
319
- market_context = await data_manager_global.get_market_context_async()
320
- ohlcv_data_list = await data_manager_global.get_ohlcv_data_for_symbols([symbol])
321
 
322
- if not ohlcv_data_list:
323
- return None
324
-
325
- ohlcv_data = ohlcv_data_list[0]
326
- ohlcv_data['reasons_for_candidacy'] = ['re-analysis']
 
 
 
 
327
 
328
- # استخدام ML للتحليل
329
- ml_processor = MLProcessor(market_context, data_manager_global, learning_engine_global)
330
- processed_data = await ml_processor.process_and_score_symbol_enhanced(ohlcv_data)
 
 
331
 
332
- if not processed_data:
333
- return None
334
 
335
- # استخدام LLM لإعادة التحليل
336
- re_analysis_decision = await llm_service_global.re_analyze_trade_async(trade_data, processed_data)
 
337
 
338
- # التحقق من وجود قرار صالح من النموذج
339
- if re_analysis_decision:
340
- await r2_service_global.save_system_logs_async({
341
- "trade_reanalyzed": True,
342
- "symbol": symbol,
343
- "action": re_analysis_decision.get('action'),
344
- "strategy": re_analysis_decision.get('strategy', 'GENERIC')
345
- })
346
 
347
- return {
348
- "symbol": symbol,
349
- "decision": re_analysis_decision,
350
- "current_price": processed_data.get('current_price')
351
- }
352
- else:
353
- print(f"⚠️ لا يوجد قرار إعادة تحليل لـ {symbol}")
354
- return None
355
 
356
- except Exception as error:
357
- print(f"❌ Error during trade re-analysis: {error}")
358
- await r2_service_global.save_system_logs_async({
359
- "reanalysis_error": True,
360
- "symbol": symbol,
361
- "error": str(error)
362
- })
363
- return None
364
-
365
- async def run_bot_cycle_async():
366
- """دورة التداول الرئيسية"""
367
- try:
368
- if not await state_manager.wait_for_initialization():
369
- print("❌ الخدمات غير مهيأة بالكامل - تخطي الدورة")
370
- return
371
-
372
- print("🔄 بدء دورة التداول...")
373
- await r2_service_global.save_system_logs_async({"cycle_started": True})
374
-
375
- if not r2_service_global.acquire_lock():
376
- print("❌ فشل الحصول على القفل - تخطي الدورة")
377
- return
378
 
379
- try:
380
- open_trades = await trade_manager_global.get_open_trades()
381
- print(f"📋 الصفقات المفتوحة: {len(open_trades)}")
382
-
383
- should_look_for_new_trade = len(open_trades) == 0
384
-
385
- # إعادة تحليل الصفقات المفتوحة
386
- if open_trades:
387
- now = datetime.now()
388
- trades_to_reanalyze = [
389
- trade for trade in open_trades
390
- if now >= datetime.fromisoformat(trade.get('expected_target_time', now.isoformat()))
391
- ]
392
-
393
- if trades_to_reanalyze:
394
- print(f"🔄 إعادة تحليل {len(trades_to_reanalyze)} صفقة")
395
- for trade in trades_to_reanalyze:
396
- result = await re_analyze_open_trade_async(trade)
397
- if result and result['decision'].get('action') == "CLOSE_TRADE":
398
- await trade_manager_global.close_trade(trade, result['current_price'], 'CLOSED_BY_REANALYSIS')
399
- should_look_for_new_trade = True
400
- elif result and result['decision'].get('action') == "UPDATE_TRADE":
401
- await trade_manager_global.update_trade(trade, result['decision'])
402
-
403
- # البحث عن صفقات جديدة إذا لزم الأمر
404
- if should_look_for_new_trade:
405
- portfolio_state = await r2_service_global.get_portfolio_state_async()
406
- current_capital = portfolio_state.get("current_capital_usd", 0)
407
-
408
- if current_capital > 1:
409
- print("🎯 البحث عن فرص تداول جديدة...")
410
- best_opportunity = await run_3_layer_analysis()
411
 
412
- if best_opportunity:
413
- print(f"✅ فتح صفقة جديدة: {best_opportunity['symbol']}")
414
- await trade_manager_global.open_trade(
415
- best_opportunity['symbol'],
416
- best_opportunity['decision'],
417
- best_opportunity['current_price']
418
- )
419
- else:
420
- print("❌ لم يتم العثور على فرص تداول مناسبة")
421
- else:
422
- print("❌ رأس المال غير كافي لفتح صفقات جديدة")
423
 
424
- finally:
425
- r2_service_global.release_lock()
426
- await r2_service_global.save_system_logs_async({
427
- "cycle_completed": True,
428
- "open_trades": len(open_trades) if 'open_trades' in locals() else 0
429
- })
430
- print("✅ اكتملت دورة التداول")
431
-
432
- except Exception as error:
433
- print(f"❌ Unhandled error in main cycle: {error}")
434
- await r2_service_global.save_system_logs_async({
435
- "cycle_error": True,
436
- "error": str(error)
437
- })
438
- if r2_service_global.lock_acquired:
439
- r2_service_global.release_lock()
440
-
441
- @asynccontextmanager
442
- async def lifespan(application: FastAPI):
443
- """إدارة دورة حياة التطبيق"""
444
- global r2_service_global, data_manager_global, llm_service_global, learning_engine_global
445
- global trade_manager_global, sentiment_analyzer_global, symbol_whale_monitor_global
446
-
447
- initialization_successful = False
448
- try:
449
- print("🚀 بدء تهيئة التطبيق...")
450
-
451
- # تهيئة الخدمات
452
- r2_service_global = R2Service()
453
- state_manager.set_service_initialized('r2_service')
454
-
455
- contracts_database = await r2_service_global.load_contracts_db_async()
456
-
457
- from whale_news_data import EnhancedWhaleMonitor
458
- symbol_whale_monitor_global = EnhancedWhaleMonitor(contracts_database, r2_service_global)
459
- state_manager.set_service_initialized('symbol_whale_monitor')
460
 
461
- data_manager_global = DataManager(contracts_database, symbol_whale_monitor_global)
462
- await data_manager_global.initialize()
463
- state_manager.set_service_initialized('data_manager')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
464
 
465
- llm_service_global = LLMService()
466
- llm_service_global.r2_service = r2_service_global # ربط R2Service مع LLMService
467
- state_manager.set_service_initialized('llm_service')
 
 
468
 
469
- sentiment_analyzer_global = SentimentAnalyzer(data_manager_global)
470
- state_manager.set_service_initialized('sentiment_analyzer')
 
 
 
 
 
 
 
 
471
 
472
- learning_engine_global = LearningEngine(r2_service_global, data_manager_global)
473
- await learning_engine_global.initialize_enhanced()
474
- state_manager.set_service_initialized('learning_engine')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
475
 
476
- trade_manager_global = TradeManager(r2_service_global, learning_engine_global, data_manager_global)
477
- state_manager.set_service_initialized('trade_manager')
 
 
 
 
 
 
 
 
 
 
 
 
478
 
479
- # بدء المهام الخلفية
480
- asyncio.create_task(monitor_market_async())
481
- asyncio.create_task(trade_manager_global.start_trade_monitoring())
482
-
483
- await r2_service_global.save_system_logs_async({"application_started": True})
484
- initialization_successful = True
485
- print("🎯 التطبيق جاهز للعمل - نظام الطبقات 3 فعال")
486
-
487
- yield
488
-
489
- except Exception as error:
490
- print(f"❌ Application startup failed: {error}")
491
- if r2_service_global:
492
- await r2_service_global.save_system_logs_async({
493
- "application_startup_failed": True,
494
- "error": str(error)
495
- })
496
- raise
497
- finally:
498
- await cleanup_on_shutdown()
499
-
500
- application = FastAPI(lifespan=lifespan)
501
-
502
- @application.get("/run-cycle")
503
- async def run_cycle_api():
504
- """تشغيل دورة التداول"""
505
- if not state_manager.initialization_complete:
506
- raise HTTPException(status_code=503, detail="الخدمات غير مهيأة بالكامل")
507
- asyncio.create_task(run_bot_cycle_async())
508
- return {"message": "Bot cycle initiated", "system": "3-Layer Analysis"}
509
-
510
- @application.get("/health")
511
- async def health_check():
512
- """فحص صحة النظام"""
513
- services_status = {
514
- "status": "healthy" if state_manager.initialization_complete else "initializing",
515
- "initialization_complete": state_manager.initialization_complete,
516
- "services_initialized": state_manager.services_initialized,
517
- "timestamp": datetime.now().isoformat(),
518
- "system_architecture": "3-Layer Analysis System",
519
- "layers": {
520
- "layer1": "Data Manager - Rapid Screening",
521
- "layer2": "ML Processor - Advanced Analysis",
522
- "layer3": "LLM Service - Deep Analysis"
523
- }
524
- }
525
- return services_status
526
-
527
- @application.get("/analyze-market")
528
- async def analyze_market_api():
529
- """تشغيل التحليل الطبقي فقط"""
530
- if not state_manager.initialization_complete:
531
- raise HTTPException(status_code=503, detail="الخدمات غير مهيأة بالكامل")
532
-
533
- result = await run_3_layer_analysis()
534
- if result:
535
- return {
536
- "opportunity_found": True,
537
- "symbol": result['symbol'],
538
- "action": result['decision'].get('action'),
539
- "confidence": result['llm_confidence'],
540
- "strategy": result['strategy']
541
- }
542
- else:
543
- return {"opportunity_found": False, "message": "No suitable opportunities found"}
544
-
545
- @application.get("/portfolio")
546
- async def get_portfolio_api():
547
- """الحصول على حالة المحفظة"""
548
- if not state_manager.initialization_complete:
549
- raise HTTPException(status_code=503, detail="الخدمات غير مهيأة بالكامل")
550
 
551
- try:
552
- portfolio_state = await r2_service_global.get_portfolio_state_async()
553
- open_trades = await trade_manager_global.get_open_trades()
554
-
555
- return {
556
- "portfolio": portfolio_state,
557
- "open_trades": open_trades,
558
- "timestamp": datetime.now().isoformat()
559
- }
560
- except Exception as e:
561
- raise HTTPException(status_code=500, detail=f"خطأ في جلب بيانات المحفظة: {str(e)}")
562
 
563
- @application.get("/system-status")
564
- async def get_system_status():
565
- """الحصول على حالة النظام التفصيلية"""
566
- monitoring_status = trade_manager_global.get_monitoring_status() if trade_manager_global else {}
567
-
568
- return {
569
- "initialization_complete": state_manager.initialization_complete,
570
- "services_initialized": state_manager.services_initialized,
571
- "market_state_ok": state.MARKET_STATE_OK,
572
- "monitoring_status": monitoring_status,
573
- "timestamp": datetime.now().isoformat()
574
- }
575
-
576
- async def cleanup_on_shutdown():
577
- """تنظيف الموارد عند الإغلاق"""
578
- global r2_service_global, data_manager_global, trade_manager_global, learning_engine_global
579
-
580
- print("🛑 Shutdown signal received. Cleaning up...")
581
-
582
- if trade_manager_global:
583
- trade_manager_global.stop_monitoring()
584
- print("✅ Trade monitoring stopped")
585
-
586
- if learning_engine_global and learning_engine_global.initialized:
587
  try:
588
- await learning_engine_global.save_weights_to_r2()
589
- await learning_engine_global.save_performance_history()
590
- print("✅ Learning engine data saved")
591
- except Exception as e:
592
- print(f"❌ Failed to save learning engine data: {e}")
593
-
594
- if data_manager_global:
595
- await data_manager_global.close()
596
- print("✅ Data manager closed")
597
-
598
- if r2_service_global:
599
- try:
600
- await r2_service_global.save_system_logs_async({"application_shutdown": True})
601
- print("✅ Shutdown log saved")
602
- except Exception as e:
603
- print(f"❌ Failed to save shutdown log: {e}")
604
-
605
- if r2_service_global.lock_acquired:
606
- r2_service_global.release_lock()
607
- print("✅ R2 lock released")
608
-
609
- def signal_handler(signum, frame):
610
- """معالج إشارات الإغلاق"""
611
- print(f"🛑 Received signal {signum}. Initiating shutdown...")
612
- asyncio.create_task(cleanup_on_shutdown())
613
- sys.exit(0)
614
-
615
- # تسجيل معالجات الإشارات
616
- signal.signal(signal.SIGINT, signal_handler)
617
- signal.signal(signal.SIGTERM, signal_handler)
618
-
619
- if __name__ == "__main__":
620
- print("🚀 Starting AI Trading Bot with 3-Layer Analysis System...")
621
- uvicorn.run(application, host="0.0.0.0", port=7860, log_level="info")
 
1
+ # data_manager.py (محدث)
2
  import os
 
 
 
 
3
  import asyncio
4
+ import httpx
5
+ import traceback
6
  import time
 
 
7
  from datetime import datetime
8
+ import ccxt
9
+ import numpy as np
10
+ import logging
11
+ from typing import List, Dict, Any
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
+ logging.getLogger("httpx").setLevel(logging.WARNING)
14
+ logging.getLogger("httpcore").setLevel(logging.WARNING)
 
 
 
15
 
16
+ class DataManager:
17
+ def __init__(self, contracts_db, whale_monitor):
18
+ self.contracts_db = contracts_db or {}
19
+ self.whale_monitor = whale_monitor
20
+
21
+ try:
22
+ self.exchange = ccxt.kucoin({
23
+ 'sandbox': False,
24
+ 'enableRateLimit': True,
25
+ 'timeout': 30000,
26
+ 'verbose': False,
27
+ })
28
+ print("✅ تم تهيئة اتصال KuCoin بنجاح")
29
+ except Exception as e:
30
+ print(f"❌ فشل تهيئة اتصال KuCoin: {e}")
31
+ self.exchange = None
32
+
33
+ self.http_client = None
34
+ self.market_cache = {}
35
+ self.last_market_load = None
36
+
37
+ async def initialize(self):
38
+ self.http_client = httpx.AsyncClient(timeout=30.0)
39
+ await self._load_markets()
40
+ print("✅ DataManager initialized - Efficient Volume-Based Screening")
41
 
42
+ async def _load_markets(self):
43
+ try:
44
+ if not self.exchange:
45
+ return
46
+
47
+ print("🔄 جلب أحدث بيانات الأسواق من KuCoin...")
48
+ self.exchange.load_markets()
49
+ self.market_cache = self.exchange.markets
50
+ self.last_market_load = datetime.now()
51
+ print(f"✅ تم تحميل {len(self.market_cache)} سوق من KuCoin")
52
+
53
+ except Exception as e:
54
+ print(f"❌ فشل تحميل بيانات الأسواق: {e}")
55
 
56
+ async def close(self):
57
+ if self.http_client:
58
+ await self.http_client.aclose()
59
+
60
+ async def get_market_context_async(self):
61
+ """جلب سياق السوق الأساسي فقط"""
 
 
 
62
  try:
63
+ sentiment_data = await self.get_sentiment_safe_async()
64
+ price_data = await self._get_prices_with_fallback()
65
+
66
+ bitcoin_price = price_data.get('bitcoin')
67
+ ethereum_price = price_data.get('ethereum')
68
+
69
+ market_context = {
70
+ 'timestamp': datetime.now().isoformat(),
71
+ 'bitcoin_price_usd': bitcoin_price,
72
+ 'ethereum_price_usd': ethereum_price,
73
+ 'fear_and_greed_index': sentiment_data.get('feargreed_value') if sentiment_data else None,
74
+ 'sentiment_class': sentiment_data.get('feargreed_class') if sentiment_data else 'NEUTRAL',
75
+ 'market_trend': self._determine_market_trend(bitcoin_price, sentiment_data),
76
+ 'btc_sentiment': self._get_btc_sentiment(bitcoin_price),
77
+ 'data_quality': 'HIGH' if bitcoin_price and ethereum_price else 'LOW'
78
+ }
79
+
80
+ return market_context
81
 
82
+ except Exception as e:
83
+ print(f"❌ فشل جلب سياق السوق: {e}")
84
+ return self._get_minimal_market_context()
85
+
86
+ async def get_sentiment_safe_async(self):
87
+ """جلب بيانات المشاعر"""
88
+ try:
89
+ async with httpx.AsyncClient(timeout=10) as client:
90
+ response = await client.get("https://api.alternative.me/fng/")
91
+ response.raise_for_status()
92
+ data = response.json()
93
 
94
+ if 'data' not in data or not data['data']:
95
+ raise ValueError("بيانات المشاعر غير متوفرة")
96
+
97
+ latest_data = data['data'][0]
98
+ return {
99
+ "feargreed_value": int(latest_data['value']),
100
+ "feargreed_class": latest_data['value_classification'],
101
+ "source": "alternative.me",
102
+ "timestamp": datetime.now().isoformat()
103
+ }
104
+ except Exception as e:
105
+ print(f"❌ فشل جلب بيانات المشاعر: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  return None
107
 
108
+ def _determine_market_trend(self, bitcoin_price, sentiment_data):
109
+ """تحديد اتجاه السوق"""
110
+ if bitcoin_price is None:
111
+ return "UNKNOWN"
112
+
113
+ score = 0
114
+ if bitcoin_price > 60000:
115
+ score += 1
116
+ elif bitcoin_price < 55000:
117
+ score -= 1
118
+
119
+ if sentiment_data and sentiment_data.get('feargreed_value') is not None:
120
+ fear_greed = sentiment_data.get('feargreed_value')
121
+ if fear_greed > 60:
122
+ score += 1
123
+ elif fear_greed < 40:
124
+ score -= 1
125
+
126
+ if score >= 1:
127
+ return "bull_market"
128
+ elif score <= -1:
129
+ return "bear_market"
130
+ else:
131
+ return "sideways_market"
132
+
133
+ def _get_btc_sentiment(self, bitcoin_price):
134
+ if bitcoin_price is None:
135
+ return 'UNKNOWN'
136
+ elif bitcoin_price > 60000:
137
+ return 'BULLISH'
138
+ elif bitcoin_price < 55000:
139
+ return 'BEARISH'
140
+ else:
141
+ return 'NEUTRAL'
142
+
143
+ async def _get_prices_with_fallback(self):
144
+ """جلب أسعار البيتكوين والإيثيريوم"""
145
+ try:
146
+ prices = await self._get_prices_from_kucoin_safe()
147
+ if prices.get('bitcoin') and prices.get('ethereum'):
148
+ return prices
149
+ return await self._get_prices_from_coingecko()
150
+ except Exception as e:
151
+ print(f"❌ فشل جلب الأسعار: {e}")
152
+ return {'bitcoin': None, 'ethereum': None}
153
+
154
+ async def _get_prices_from_kucoin_safe(self):
155
+ if not self.exchange:
156
+ return {'bitcoin': None, 'ethereum': None}
157
+
158
+ try:
159
+ prices = {'bitcoin': None, 'ethereum': None}
160
+
161
+ btc_ticker = self.exchange.fetch_ticker('BTC/USDT')
162
+ btc_price = float(btc_ticker.get('last', 0)) if btc_ticker.get('last') else None
163
+ if btc_price and btc_price > 0:
164
+ prices['bitcoin'] = btc_price
165
+
166
+ eth_ticker = self.exchange.fetch_ticker('ETH/USDT')
167
+ eth_price = float(eth_ticker.get('last', 0)) if eth_ticker.get('last') else None
168
+ if eth_price and eth_price > 0:
169
+ prices['ethereum'] = eth_price
170
+
171
+ return prices
172
+
173
+ except Exception as e:
174
+ print(f"❌ خطأ في جلب الأسعار من KuCoin: {e}")
175
+ return {'bitcoin': None, 'ethereum': None}
176
 
177
+ async def _get_prices_from_coingecko(self):
178
+ """الاحتياطي: جلب الأسعار من CoinGecko"""
179
+ try:
180
+ await asyncio.sleep(0.5)
181
+ url = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd"
182
+
183
+ # إضافة headers لتجنب rate limiting
184
+ headers = {
185
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
186
+ 'Accept': 'application/json'
187
+ }
188
+
189
+ async with httpx.AsyncClient(headers=headers) as client:
190
+ response = await client.get(url, timeout=10)
191
+
192
+ if response.status_code == 429:
193
+ print("⏰ Rate limit من CoinGecko - الانتظار 2 ثانية")
194
+ await asyncio.sleep(2)
195
+ response = await client.get(url, timeout=10)
196
+
197
+ response.raise_for_status()
198
+ data = response.json()
199
+
200
+ btc_price = data.get('bitcoin', {}).get('usd')
201
+ eth_price = data.get('ethereum', {}).get('usd')
202
+
203
+ if btc_price and eth_price:
204
+ return {'bitcoin': btc_price, 'ethereum': eth_price}
205
+ else:
206
+ return {'bitcoin': None, 'ethereum': None}
207
+
208
+ except Exception as e:
209
+ print(f"❌ فشل جلب الأسعار من CoinGecko: {e}")
210
+ return {'bitcoin': None, 'ethereum': None}
211
 
212
+ def _get_minimal_market_context(self):
213
+ """سياق سوق بدائي عند الفشل"""
214
+ return {
215
+ 'timestamp': datetime.now().isoformat(),
216
+ 'data_available': False,
217
+ 'market_trend': 'UNKNOWN',
218
+ 'btc_sentiment': 'UNKNOWN',
219
+ 'data_quality': 'LOW'
220
+ }
221
 
222
+ async def layer1_rapid_screening(self) -> List[Dict[str, Any]]:
223
+ """
224
+ الطبقة 1: فحص سريع - جلب أفضل 200 عملة حسب الحجم مباشرة
225
+ """
226
+ print("📊 الطبقة 1: جلب أفضل 200 عملة حسب حجم التداول...")
227
 
228
+ # المحاولة 1: الطريقة المثلى - استخدام fetch_tickers
229
+ print(" 🔍 المحاولة 1: جلب جميع التاكرز مرة واحدة...")
230
+ volume_data = await self._get_volume_data_optimal()
231
 
232
+ if not volume_data:
233
+ # المحاولة 2: الطريقة البديلة - استخدام API المباشر
234
+ print(" 🔄 المحاولة 2: استخدام API المباشر...")
235
+ volume_data = await self._get_volume_data_direct_api()
 
 
 
 
 
 
 
 
 
 
 
 
 
236
 
237
+ if not volume_data:
238
+ # المحاولة 3: الطريقة التقليدية (الاحتياطية)
239
+ print(" ⚠️ المحاولة 3: استخدام الطريقة التقليدية...")
240
+ volume_data = await self._get_volume_data_traditional()
241
 
242
+ if not volume_data:
243
+ print("❌ فشل جميع محاولات جلب بيانات الأحجام")
244
+ return []
 
245
 
246
+ # أخذ أفضل 200 عملة حسب الحجم فقط
247
+ volume_data.sort(key=lambda x: x['dollar_volume'], reverse=True)
248
+ top_200_by_volume = volume_data[:200]
249
 
250
+ print(f"✅ تم اختيار أفضل {len(top_200_by_volume)} عملة حسب الحجم")
 
 
 
 
251
 
252
+ # المرحلة 2: تطبيق المؤشرات الأخرى على الـ200 فقط
253
+ print(" 📈 تطبيق المؤشرات المتقدمة على أفضل 200 عملة...")
254
+ final_candidates = await self._apply_advanced_indicators(top_200_by_volume)
255
 
256
+ print(f"🎯 تم تحليل {len(final_candidates)} عملة للطبقة 2")
 
 
 
257
 
258
+ # عرض أفضل 15 عملة
259
+ print("🏆 أفضل 15 عملة من الطبقة 1:")
260
+ for i, candidate in enumerate(final_candidates[:15]):
261
+ score = candidate.get('layer1_score', 0)
262
+ volume = candidate.get('dollar_volume', 0)
263
+ change = candidate.get('price_change_24h', 0)
264
+ print(f" {i+1:2d}. {candidate['symbol']}: {score:.3f} | ${volume:>10,.0f} | {change:>+6.1f}%")
265
 
266
+ return final_candidates
 
 
 
 
 
 
 
 
 
267
 
268
+ async def _get_volume_data_optimal(self) -> List[Dict[str, Any]]:
269
+ """الطريقة المثلى: استخدام fetch_tickers لجميع البيانات مرة واحدة"""
270
+ try:
271
+ if not self.exchange:
272
+ return []
273
+
274
+ print(" 📊 جلب جميع بيانات التداول مرة واحدة...")
275
+ tickers = self.exchange.fetch_tickers()
276
+
277
+ volume_data = []
278
+ processed = 0
279
+
280
+ for symbol, ticker in tickers.items():
281
+ # تصفية أزواج USDT النشطة فقط
282
+ if not symbol.endswith('/USDT'):
283
+ continue
284
+
285
+ if not ticker.get('active', True):
286
+ continue
287
+
288
+ # استخدام quoteVolume (الحجم بالدولار) إذا متوفر
289
+ current_price = ticker.get('last', 0)
290
+ quote_volume = ticker.get('quoteVolume', 0)
291
+
292
+ if quote_volume > 0:
293
+ dollar_volume = quote_volume
294
+ else:
295
+ # fallback: baseVolume * السعر
296
+ base_volume = ticker.get('baseVolume', 0)
297
+ dollar_volume = base_volume * current_price
298
+
299
+ # فلترة أولية: تجاهل العملات ذات الحجم المنخفض جداً
300
+ if dollar_volume < 50000: # أقل من 50K دولار
301
+ continue
302
+
303
+ volume_data.append({
304
+ 'symbol': symbol,
305
+ 'dollar_volume': dollar_volume,
306
+ 'current_price': current_price,
307
+ 'volume_24h': ticker.get('baseVolume', 0),
308
+ 'price_change_24h': (ticker.get('percentage', 0) or 0) * 100
309
+ })
310
+
311
+ processed += 1
312
+
313
+ print(f" ✅ تم معالجة {processed} عملة من أصل {len(tickers)}")
314
+ return volume_data
315
+
316
+ except Exception as e:
317
+ print(f" ❌ فشل الطريقة المثلى: {e}")
318
+ return []
319
+
320
+ async def _get_volume_data_direct_api(self) -> List[Dict[str, Any]]:
321
+ """الطريقة الثانية: استخدام KuCoin API مباشرة"""
322
+ try:
323
+ print(" 🌐 الاتصال بـ KuCoin API مباشرة...")
324
+ url = "https://api.kucoin.com/api/v1/market/allTickers"
325
+
326
+ async with httpx.AsyncClient(timeout=15) as client:
327
+ response = await client.get(url)
328
+ response.raise_for_status()
329
+ data = response.json()
330
+
331
+ if data.get('code') != '200000':
332
+ raise ValueError(f"استجابة API غير متوقعة: {data.get('code')}")
333
+
334
+ tickers = data['data']['ticker']
335
+ volume_data = []
336
+
337
+ for ticker in tickers:
338
+ symbol = ticker['symbol']
339
+
340
+ # تصفية أزواج USDT فقط
341
+ if not symbol.endswith('USDT'):
342
+ continue
343
+
344
+ # تحويل الرمز للتنسيق القياسي (BTC-USDT → BTC/USDT)
345
+ formatted_symbol = symbol.replace('-', '/')
346
+
347
+ try:
348
+ dollar_volume = float(ticker['volValue']) # الحجم بالدولار
349
+ current_price = float(ticker['last'])
350
+ price_change = float(ticker['changeRate']) * 100
351
+
352
+ if dollar_volume >= 50000:
353
+ volume_data.append({
354
+ 'symbol': formatted_symbol,
355
+ 'dollar_volume': dollar_volume,
356
+ 'current_price': current_price,
357
+ 'volume_24h': float(ticker['vol']),
358
+ 'price_change_24h': price_change
359
+ })
360
+ except (ValueError, KeyError) as e:
361
+ continue
362
+
363
+ print(f" ✅ تم جلب {len(volume_data)} عملة من API المباشر")
364
+ return volume_data
365
+
366
+ except Exception as e:
367
+ print(f" ❌ فشل API المباشر: {e}")
368
+ return []
369
 
370
+ async def _get_volume_data_traditional(self) -> List[Dict[str, Any]]:
371
+ """الطريقة التقليدية: جلب كل رمز على حدة (الاحتياطي)"""
372
+ try:
373
+ if not self.exchange or not self.market_cache:
374
+ return []
375
+
376
+ usdt_symbols = [
377
+ symbol for symbol in self.market_cache.keys()
378
+ if symbol.endswith('/USDT') and self.market_cache[symbol].get('active', False)
379
+ ]
380
+
381
+ print(f" 🔄 معالجة {len(usdt_symbols)} عملة (طريقة تقليدية)...")
382
+
383
+ volume_data = []
384
+ processed = 0
385
+
386
+ # معالجة دفعات لتجنب rate limits
387
+ batch_size = 50
388
+ for i in range(0, len(usdt_symbols), batch_size):
389
+ batch = usdt_symbols[i:i + batch_size]
390
+ batch_tasks = [self._process_single_symbol(sym) for sym in batch]
391
+ batch_results = await asyncio.gather(*batch_tasks, return_exceptions=True)
392
+
393
+ for result in batch_results:
394
+ if isinstance(result, dict):
395
+ volume_data.append(result)
396
+
397
+ processed += len(batch)
398
+ print(f" ✅ تم معالجة {processed}/{len(usdt_symbols)} عملة...")
399
+
400
+ # انتظار قصير بين الدفعات
401
+ if i + batch_size < len(usdt_symbols):
402
+ await asyncio.sleep(1)
403
+
404
+ print(f" ✅ تم جمع بيانات {len(volume_data)} عملة مؤهلة")
405
+ return volume_data
406
 
407
+ except Exception as e:
408
+ print(f" فشل الطريقة التقليدية: {e}")
409
+ return []
 
 
410
 
411
+ async def _process_single_symbol(self, symbol: str) -> Dict[str, Any]:
412
+ """معالجة رمز واحد لجلب بيانات الحجم"""
413
+ try:
414
+ ticker = self.exchange.fetch_ticker(symbol)
415
+ if not ticker:
416
+ return None
417
+
418
+ current_price = ticker.get('last', 0)
419
+ quote_volume = ticker.get('quoteVolume', 0)
420
+
421
+ if quote_volume > 0:
422
+ dollar_volume = quote_volume
423
+ else:
424
+ base_volume = ticker.get('baseVolume', 0)
425
+ dollar_volume = base_volume * current_price
426
+
427
+ if dollar_volume < 50000:
428
+ return None
429
+
430
+ return {
431
+ 'symbol': symbol,
432
+ 'dollar_volume': dollar_volume,
433
+ 'current_price': current_price,
434
+ 'volume_24h': ticker.get('baseVolume', 0),
435
+ 'price_change_24h': (ticker.get('percentage', 0) or 0) * 100
436
+ }
437
+ except Exception:
438
+ return None
439
+
440
+ async def _apply_advanced_indicators(self, volume_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
441
+ """تطبيق المؤشرات المتقدمة على أفضل العملات حسب الحجم"""
442
+ candidates = []
443
 
444
+ print(" 🔧 تطبيق مؤشرات الزخم والتقلب وقوة السعر...")
445
+
446
+ for i, symbol_data in enumerate(volume_data):
447
  try:
448
+ symbol = symbol_data['symbol']
 
449
 
450
+ # جلب بيانات إضافية للرمز
451
+ detailed_data = await self._get_detailed_symbol_data(symbol)
452
+ if not detailed_data:
 
453
  continue
454
 
455
+ # دمج البيانات
456
+ symbol_data.update(detailed_data)
 
 
 
 
457
 
458
+ # حساب الدرجة النهائية
459
+ score = self._calculate_advanced_score(symbol_data)
460
+ symbol_data['layer1_score'] = score
461
 
462
+ candidates.append(symbol_data)
 
463
 
464
+ if (i + 1) % 50 == 0:
465
+ print(f" ✅ تم تحليل {i + 1}/{len(volume_data)} عملة")
 
 
 
 
 
 
 
 
 
 
 
 
466
 
 
 
 
 
 
467
  except Exception as e:
 
468
  continue
469
 
470
+ # ترتيب المرشحين حسب الدرجة النهائية
471
+ candidates.sort(key=lambda x: x.get('layer1_score', 0), reverse=True)
472
+ return candidates
473
+
474
+ async def _get_detailed_symbol_data(self, symbol: str) -> Dict[str, Any]:
475
+ """جلب بيانات تفصيلية للرمز"""
476
+ try:
477
+ ticker = self.exchange.fetch_ticker(symbol)
478
+ if not ticker:
479
+ return None
480
+
481
+ current_price = ticker.get('last', 0)
482
+ high_24h = ticker.get('high', 0)
483
+ low_24h = ticker.get('low', 0)
484
+ open_price = ticker.get('open', 0)
485
+ price_change_24h = (ticker.get('percentage', 0) or 0) * 100
486
+
487
+ # حساب المؤشرات المتقدمة
488
+ volatility = self._calculate_volatility(high_24h, low_24h, current_price)
489
+ price_strength = self._calculate_price_strength(current_price, open_price, price_change_24h)
490
+ momentum = self._calculate_momentum(price_change_24h)
491
+
492
+ return {
493
+ 'price_change_24h': price_change_24h,
494
+ 'high_24h': high_24h,
495
+ 'low_24h': low_24h,
496
+ 'open_price': open_price,
497
+ 'volatility': volatility,
498
+ 'price_strength': price_strength,
499
+ 'momentum': momentum,
500
+ 'reasons': []
501
+ }
502
+
503
+ except Exception as e:
504
  return None
505
+
506
+ def _calculate_advanced_score(self, symbol_data: Dict[str, Any]) -> float:
507
+ """حساب درجة متقدمة تجمع بين الحجم والمؤشرات الأخرى"""
508
+ dollar_volume = symbol_data.get('dollar_volume', 0)
509
+ price_change = symbol_data.get('price_change_24h', 0)
510
+ volatility = symbol_data.get('volatility', 0)
511
+ price_strength = symbol_data.get('price_strength', 0)
512
+ momentum = symbol_data.get('momentum', 0)
513
+
514
+ # 1. درجة الحجم (40%) - الأهم
515
+ volume_score = self._calculate_volume_score(dollar_volume)
516
+
517
+ # 2. درجة الزخم (25%)
518
+ momentum_score = momentum
519
+
520
+ # 3. درجة التقلب (20%)
521
+ volatility_score = self._calculate_volatility_score(volatility)
522
+
523
+ # 4. درجة قوة السعر (15%)
524
+ strength_score = price_strength
525
+
526
+ # الدرجة النهائية
527
+ final_score = (
528
+ volume_score * 0.40 +
529
+ momentum_score * 0.25 +
530
+ volatility_score * 0.20 +
531
+ strength_score * 0.15
532
+ )
533
+
534
+ # تحديث أسباب الترشيح
535
+ reasons = []
536
+ if volume_score >= 0.7:
537
+ reasons.append('high_volume')
538
+ if momentum_score >= 0.7:
539
+ reasons.append('strong_momentum')
540
+ if volatility_score >= 0.7:
541
+ reasons.append('good_volatility')
542
+
543
+ symbol_data['reasons'] = reasons
544
+
545
+ return final_score
546
+
547
+ def _calculate_volume_score(self, dollar_volume: float) -> float:
548
+ """حساب درجة الحجم"""
549
+ if dollar_volume >= 10000000: # 10M+
550
+ return 1.0
551
+ elif dollar_volume >= 5000000: # 5M+
552
+ return 0.9
553
+ elif dollar_volume >= 2000000: # 2M+
554
+ return 0.8
555
+ elif dollar_volume >= 1000000: # 1M+
556
+ return 0.7
557
+ elif dollar_volume >= 500000: # 500K+
558
+ return 0.6
559
+ elif dollar_volume >= 250000: # 250K+
560
+ return 0.5
561
+ elif dollar_volume >= 100000: # 100K+
562
+ return 0.4
563
+ else:
564
+ return 0.3
565
+
566
+ def _calculate_volatility(self, high_24h: float, low_24h: float, current_price: float) -> float:
567
+ """حساب التقلب"""
568
+ if current_price == 0:
569
+ return 0
570
+ return (high_24h - low_24h) / current_price
571
+
572
+ def _calculate_volatility_score(self, volatility: float) -> float:
573
+ """حساب درجة التقلب"""
574
+ if 0.02 <= volatility <= 0.15: # تقلب مثالي 2%-15%
575
+ return 1.0
576
+ elif 0.01 <= volatility <= 0.20: # مقبول 1%-20%
577
+ return 0.8
578
+ elif volatility <= 0.01: # قليل جداً
579
+ return 0.4
580
+ elif volatility > 0.20: # عالي جداً
581
+ return 0.3
582
+ else:
583
+ return 0.5
584
+
585
+ def _calculate_price_strength(self, current_price: float, open_price: float, price_change: float) -> float:
586
+ """حساب قوة السعر"""
587
+ if open_price == 0:
588
+ return 0.5
589
+
590
+ # قوة السعر تعتمد على المسافة من سعر الافتتاح ونسبة التغير
591
+ distance_from_open = abs(current_price - open_price) / open_price
592
+ change_strength = min(abs(price_change) / 50, 1.0)
593
+
594
+ return (distance_from_open * 0.6 + change_strength * 0.4)
595
+
596
+ def _calculate_momentum(self, price_change: float) -> float:
597
+ """حساب الزخم"""
598
+ if price_change >= 15: # +15%+
599
+ return 1.0
600
+ elif price_change >= 10: # +10%+
601
+ return 0.9
602
+ elif price_change >= 5: # +5%+
603
+ return 0.8
604
+ elif price_change >= 2: # +2%+
605
+ return 0.7
606
+ elif price_change >= 0: # موجب
607
+ return 0.6
608
+ elif price_change >= -5: # حتى -5%
609
+ return 0.5
610
+ elif price_change >= -10: # حتى -10%
611
+ return 0.4
612
+ else: # أكثر من -10%
613
+ return 0.3
614
+
615
+ async def get_ohlcv_data_for_symbols(self, symbols: List[str]) -> List[Dict[str, Any]]:
616
+ """
617
+ جلب بيانات OHLCV كاملة للرموز المحددة مع جميع الإطارات الزمنية بشكل متوازي
618
+ """
619
+ print(f"📊 جلب بيانات OHLCV كاملة لـ {len(symbols)} عملة بشكل متوازي...")
620
+
621
+ # تقسيم الرموز إلى دفعات لتجنب rate limits
622
+ batch_size = 15 # تقليل حجم الدفعة لتحسين الاستقرار
623
+ batches = [symbols[i:i + batch_size] for i in range(0, len(symbols), batch_size)]
624
+
625
+ all_results = []
626
+
627
+ for batch_num, batch in enumerate(batches):
628
+ print(f" 🔄 معالجة الدفعة {batch_num + 1}/{len(batches)} ({len(batch)} عملة)...")
629
+
630
+ # إنشاء مهام للدفعة الحالية بشكل متوازي
631
+ batch_tasks = []
632
+ for symbol in batch:
633
+ task = asyncio.create_task(self._fetch_complete_ohlcv_parallel(symbol))
634
+ batch_tasks.append(task)
635
+
636
+ # انتظار انتهاء جميع مهام الدفعة الحالية
637
+ batch_results = await asyncio.gather(*batch_tasks, return_exceptions=True)
638
+
639
+ # معالجة نتائج الدفعة
640
+ successful_count = 0
641
+ for i, result in enumerate(batch_results):
642
+ symbol = batch[i]
643
+ if isinstance(result, Exception):
644
+ print(f" ❌ فشل جلب بيانات {symbol}")
645
+ elif result is not None:
646
+ all_results.append(result)
647
+ successful_count += 1
648
+ timeframes = result.get('successful_timeframes', 0)
649
+ print(f" ✅ {symbol}: {timeframes}/6 أطر زمنية")
650
+ else:
651
+ print(f" ❌ فشل جلب بيانات {symbol}: لا توجد بيانات كافية")
652
 
653
+ print(f" ✅ اكتملت الدفعة {batch_num + 1}: {successful_count}/{len(batch)} ناجحة")
654
+
655
+ # انتظار قصير بين الدفعات لتجنب rate limits
656
+ if batch_num < len(batches) - 1:
657
+ await asyncio.sleep(1) # زيادة وقت الانتظار
 
 
 
658
 
659
+ print(f"✅ تم تجميع بيانات OHLCV كاملة لـ {len(all_results)} عملة من أصل {len(symbols)}")
660
+ return all_results
 
 
 
661
 
662
+ async def _fetch_complete_ohlcv_parallel(self, symbol: str) -> Dict[str, Any]:
663
+ """جلب بيانات OHLCV كاملة مع جميع الإطارات الزمنية بشكل متوازي"""
664
+ try:
665
+ ohlcv_data = {}
 
 
 
 
666
 
667
+ # جلب 200 شمعة لكل إطار زمني مع تحسين التعامل مع الأخطاء
668
+ timeframes = [
669
+ ('5m', 200),
670
+ ('15m', 200),
671
+ ('1h', 200),
672
+ ('4h', 200),
673
+ ('1d', 200),
674
+ ('1w', 200),
675
+ ]
676
 
677
+ # إنشاء مهام لجميع الإطارات الزمنية بشكل متوازي
678
+ timeframe_tasks = []
679
+ for timeframe, limit in timeframes:
680
+ task = asyncio.create_task(self._fetch_single_timeframe_improved(symbol, timeframe, limit))
681
+ timeframe_tasks.append(task)
682
 
683
+ # انتظار جميع المهام
684
+ timeframe_results = await asyncio.gather(*timeframe_tasks, return_exceptions=True)
685
 
686
+ # تحسين: قبول البيانات حتى لو كانت غير مكتملة
687
+ successful_timeframes = 0
688
+ min_required_timeframes = 2 # تخفيف الشرط من 3 إلى 2 أطر زمنية
689
 
690
+ # معالجة النتائج
691
+ for i, (timeframe, limit) in enumerate(timeframes):
692
+ result = timeframe_results[i]
 
 
 
 
 
693
 
694
+ if isinstance(result, Exception):
695
+ continue
 
 
 
 
 
 
696
 
697
+ if result and len(result) >= 10: # تخفيف الشرط من 50 إلى 10 شموع
698
+ ohlcv_data[timeframe] = result
699
+ successful_timeframes += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
700
 
701
+ # تحسين: قبول العملة إذا كان لديها عدد كافٍ من الأطر الزمنية
702
+ if successful_timeframes >= min_required_timeframes and ohlcv_data:
703
+ try:
704
+ ticker = self.exchange.fetch_ticker(symbol)
705
+ current_price = ticker.get('last', 0) if ticker else 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
706
 
707
+ result_data = {
708
+ 'symbol': symbol,
709
+ 'ohlcv': ohlcv_data,
710
+ 'raw_ohlcv': ohlcv_data, # ✅ إضافة البيانات الخام مباشرة
711
+ 'current_price': current_price,
712
+ 'timestamp': datetime.now().isoformat(),
713
+ 'successful_timeframes': successful_timeframes
714
+ }
 
 
 
715
 
716
+ return result_data
717
+
718
+ except Exception as price_error:
719
+ return None
720
+ else:
721
+ return None
722
+
723
+ except Exception as e:
724
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
725
 
726
+ async def _fetch_single_timeframe_improved(self, symbol: str, timeframe: str, limit: int):
727
+ """جلب بيانات إطار زمني واحد مع تحسين التعامل مع الأخطاء"""
728
+ max_retries = 3
729
+ retry_delay = 2
730
+
731
+ for attempt in range(max_retries):
732
+ try:
733
+ ohlcv_data = self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
734
+
735
+ if ohlcv_data and len(ohlcv_data) > 0:
736
+ return ohlcv_data
737
+ else:
738
+ return []
739
+
740
+ except Exception as e:
741
+ if attempt < max_retries - 1:
742
+ await asyncio.sleep(retry_delay * (attempt + 1))
743
+ else:
744
+ return []
745
 
746
+ async def get_latest_price_async(self, symbol):
747
+ """جلب السعر الحالي لعملة محددة"""
748
+ try:
749
+ if not self.exchange:
750
+ return None
751
 
752
+ ticker = self.exchange.fetch_ticker(symbol)
753
+ current_price = ticker.get('last')
754
+
755
+ if current_price:
756
+ return float(current_price)
757
+ else:
758
+ return None
759
+
760
+ except Exception as e:
761
+ return None
762
 
763
+ async def get_available_symbols(self):
764
+ """الحصول على جميع الرموز المتاحة"""
765
+ try:
766
+ if not self.exchange:
767
+ return []
768
+
769
+ if not self.market_cache:
770
+ await self._load_markets()
771
+
772
+ usdt_symbols = [
773
+ symbol for symbol in self.market_cache.keys()
774
+ if symbol.endswith('/USDT') and self.market_cache[symbol].get('active', False)
775
+ ]
776
+
777
+ return usdt_symbols
778
+
779
+ except Exception as e:
780
+ print(f"❌ خطأ في جلب الرموز المتاحة: {e}")
781
+ return []
782
 
783
+ async def validate_symbol(self, symbol):
784
+ """التحقق من صحة الرمز"""
785
+ try:
786
+ if not self.exchange:
787
+ return False
788
+
789
+ if not self.market_cache:
790
+ await self._load_markets()
791
+
792
+ return symbol in self.market_cache and self.market_cache[symbol].get('active', False)
793
+
794
+ except Exception as e:
795
+ print(f"❌ خطأ في التحقق من الرمز {symbol}: {e}")
796
+ return False
797
 
798
+ # === الدوال الجديدة لدعم بيانات الحيتان ===
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
799
 
800
+ async def get_whale_data_for_symbol(self, symbol):
801
+ """جلب بيانات الحيتان لعملة محددة"""
802
+ try:
803
+ if self.whale_monitor:
804
+ whale_data = await self.whale_monitor.get_symbol_whale_activity(symbol)
805
+ return whale_data
806
+ else:
807
+ return None
808
+ except Exception as e:
809
+ return None
 
810
 
811
+ async def get_whale_trading_signal(self, symbol, whale_data, market_context):
812
+ """جلب إشارة التداول بناءً على بيانات الحيتان"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
813
  try:
814
+ if self.whale_monitor:
815
+ return await self.whale_monitor.generate_whale_trading_signal(symbol, whale_data, market_context)
816
+ else:
817
+ return {
818
+ 'action': 'HOLD',
819
+ 'confidence': 0.3,
820
+ 'reason': 'Whale monitor not available',
821
+ 'source': 'whale_analysis'
822
+ }
823
+ except Exception as e:
824
+ return {
825
+ 'action': 'HOLD',
826
+ 'confidence': 0.3,
827
+ 'reason': f'Error: {str(e)}',
828
+ 'source': 'whale_analysis'
829
+ }
830
+
831
+ print("✅ DataManager loaded - Parallel OHLCV Fetching System with Whale Data Support & Improved Error Handling")