Update app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# app.py (محدث)
|
| 2 |
import os
|
| 3 |
import traceback
|
| 4 |
import signal
|
|
@@ -26,8 +26,7 @@ except ImportError as e:
|
|
| 26 |
print(f"❌ خطأ في استيراد الوحدات: {e}")
|
| 27 |
sys.exit(1)
|
| 28 |
|
| 29 |
-
#
|
| 30 |
-
# ...
|
| 31 |
r2_service_global = None
|
| 32 |
data_manager_global = None
|
| 33 |
llm_service_global = None
|
|
@@ -68,8 +67,6 @@ class StateManager:
|
|
| 68 |
|
| 69 |
state_manager = StateManager()
|
| 70 |
|
| 71 |
-
# (initialize_services و monitor_market_async كما هي)
|
| 72 |
-
# ...
|
| 73 |
async def initialize_services():
|
| 74 |
"""تهيئة جميع الخدمات بشكل منفصل"""
|
| 75 |
global r2_service_global, data_manager_global, llm_service_global
|
|
@@ -116,8 +113,6 @@ async def monitor_market_async():
|
|
| 116 |
except Exception as e: print(f"❌ فشل تشغيل مراقبة السوق: {e}")
|
| 117 |
|
| 118 |
|
| 119 |
-
# 🔴 --- تعديل: إزالة Semaphore من هنا --- 🔴
|
| 120 |
-
# منظم السرعة الآن داخل مهمة جلب الحيتان ومهمة معالجة ML
|
| 121 |
async def process_batch_parallel(batch, ml_processor, batch_num, total_batches, preloaded_whale_data):
|
| 122 |
"""
|
| 123 |
(معدلة) معالجة دفعة من الرموز بشكل متوازي وإرجاع نتائج مفصلة
|
|
@@ -173,8 +168,6 @@ async def process_batch_parallel(batch, ml_processor, batch_num, total_batches,
|
|
| 173 |
return {'success': [], 'low_score': [], 'failures': []}
|
| 174 |
|
| 175 |
|
| 176 |
-
# 🔴 --- بدء التعديل الجوهري --- 🔴
|
| 177 |
-
# تم إعادة هيكلة هذه الدالة بالكامل
|
| 178 |
async def run_3_layer_analysis():
|
| 179 |
"""
|
| 180 |
(معدلة) تشغيل النظام الطبقي (مع فصل جلب بيانات الحيتان)
|
|
@@ -210,8 +203,7 @@ async def run_3_layer_analysis():
|
|
| 210 |
|
| 211 |
# مهمة جلب بيانات الحيتان
|
| 212 |
async def fetch_whale_data_task(symbols, results_dict):
|
| 213 |
-
|
| 214 |
-
WHALE_FETCH_CONCURRENCY = 3 # عدد طلبات الحيتان المتزامنة
|
| 215 |
semaphore = asyncio.Semaphore(WHALE_FETCH_CONCURRENCY)
|
| 216 |
tasks = []
|
| 217 |
|
|
@@ -221,18 +213,16 @@ async def run_3_layer_analysis():
|
|
| 221 |
data = await data_manager_global.get_whale_data_for_symbol(symbol)
|
| 222 |
if data:
|
| 223 |
results_dict[symbol] = data
|
| 224 |
-
# else: # Optional: Log if data is None
|
| 225 |
-
# print(f" ⚠️ [Whale Fetch] No whale data returned for {symbol}")
|
| 226 |
except Exception as e:
|
| 227 |
print(f" ❌ [Whale Fetch] فشل جلب بيانات الحيتان لـ {symbol}: {e}")
|
| 228 |
-
results_dict[symbol] = {'data_available': False, 'error': str(e)}
|
| 229 |
|
| 230 |
for symbol in symbols:
|
| 231 |
tasks.append(asyncio.create_task(get_data_with_semaphore(symbol)))
|
| 232 |
|
| 233 |
-
await asyncio.gather(*tasks)
|
| 234 |
|
| 235 |
-
# تشغيل مهمة جلب الحيتان في الخلفية
|
| 236 |
whale_fetcher_task = asyncio.create_task(fetch_whale_data_task(layer1_symbols, preloaded_whale_data_dict))
|
| 237 |
print(" ⏳ مهمة جلب بيانات الحيتان تعمل في الخلفية...")
|
| 238 |
# 🔴 --- نهاية الطبقة 1.5 --- 🔴
|
|
@@ -301,7 +291,6 @@ async def run_3_layer_analysis():
|
|
| 301 |
|
| 302 |
# 9. تجميع النتائج (كما كان، لكن بيانات الحيتان موجودة الآن)
|
| 303 |
print("🔄 تجميع جميع النتائج...")
|
| 304 |
-
# ... (الكود لتجميع layer2_candidates, all_low_score_candidates, all_failed_candidates كما هو) ...
|
| 305 |
for batch_result in ml_results_list:
|
| 306 |
for success_item in batch_result['success']:
|
| 307 |
symbol = success_item['symbol']
|
|
@@ -309,16 +298,14 @@ async def run_3_layer_analysis():
|
|
| 309 |
if l1_data:
|
| 310 |
success_item['reasons_for_candidacy'] = l1_data.get('reasons', [])
|
| 311 |
success_item['layer1_score'] = l1_data.get('layer1_score', 0)
|
| 312 |
-
# Ensure whale data from preloading is in the final result item
|
| 313 |
if symbol in preloaded_whale_data_dict:
|
| 314 |
success_item['whale_data'] = preloaded_whale_data_dict[symbol]
|
| 315 |
-
elif 'whale_data' not in success_item:
|
| 316 |
success_item['whale_data'] = {'data_available': False, 'reason': 'Failed during preload'}
|
| 317 |
layer2_candidates.append(success_item)
|
| 318 |
all_low_score_candidates.extend(batch_result['low_score'])
|
| 319 |
all_failed_candidates.extend(batch_result['failures'])
|
| 320 |
|
| 321 |
-
|
| 322 |
print(f"✅ اكتمل التحليل المتقدم: {len(layer2_candidates)} نجاح (عالي) | {len(all_low_score_candidates)} نجاح (منخفض) | {len(all_failed_candidates)} فشل")
|
| 323 |
|
| 324 |
if not layer2_candidates: print("❌ لم يتم العثور على مرشحين في الطبقة 2")
|
|
@@ -330,7 +317,6 @@ async def run_3_layer_analysis():
|
|
| 330 |
print(f"🎯 تم اختيار {len(final_layer2_candidates)} عملة للطبقة 3 (الأقوى فقط)")
|
| 331 |
await r2_service_global.save_candidates_async(final_layer2_candidates)
|
| 332 |
print("\n🏆 أفضل 10 عملات من الطبقة 2:")
|
| 333 |
-
# ... (كود عرض أفضل 10 كما هو، سيشمل بيانات الحيتان الآن) ...
|
| 334 |
for i, candidate in enumerate(final_layer2_candidates):
|
| 335 |
score = candidate.get('enhanced_final_score', 0)
|
| 336 |
strategy = candidate.get('target_strategy', 'GENERIC')
|
|
@@ -348,13 +334,9 @@ async def run_3_layer_analysis():
|
|
| 348 |
print(f" 🐋 حيتان: {signal.get('action', 'HOLD')} (ثقة: {signal.get('confidence', 0):.2f}){' ⚠️' if signal.get('critical_alert') else ''}")
|
| 349 |
elif whale_data and whale_data.get('error'):
|
| 350 |
print(f" 🐋 حيتان: خطأ ({whale_data.get('error')[:50]}...)")
|
| 351 |
-
# else:
|
| 352 |
-
# print(f" 🐋 حيتان: غير متاحة")
|
| 353 |
-
|
| 354 |
|
| 355 |
# 11. الطبقة 3: التحليل بالنموذج الضخم (كما كان)
|
| 356 |
print("\n🧠 الطبقة 3: التحليل بالنموذج الضخم (LLMService)...")
|
| 357 |
-
# ... (كود الطبقة 3 كما هو) ...
|
| 358 |
for candidate in final_layer2_candidates:
|
| 359 |
try:
|
| 360 |
symbol = candidate['symbol']
|
|
@@ -383,7 +365,6 @@ async def run_3_layer_analysis():
|
|
| 383 |
print(f" ⚠️ {symbol}: لا يوجد قرار تداول من النموذج الضخم ({action})")
|
| 384 |
except Exception as e: print(f"❌ خطأ في تحليل النموذج الضخم لـ {candidate.get('symbol')}: {e}"); continue
|
| 385 |
|
| 386 |
-
|
| 387 |
if final_opportunities:
|
| 388 |
final_opportunities.sort(key=lambda x: (x['llm_confidence'] + x['enhanced_score']) / 2, reverse=True)
|
| 389 |
print(f"\n🏆 النظام الطبقي اكتمل: {len(final_opportunities)} فرصة تداول")
|
|
@@ -392,7 +373,6 @@ async def run_3_layer_analysis():
|
|
| 392 |
|
| 393 |
# 12. سجل التدقيق (كما كان)
|
| 394 |
try:
|
| 395 |
-
# ... (كود سجل التدقيق كما هو، سيستخدم البيانات المجمعة الآن) ...
|
| 396 |
top_10_detailed_summary = []
|
| 397 |
for c in final_layer2_candidates:
|
| 398 |
whale_summary = "Not Available"
|
|
@@ -437,10 +417,7 @@ async def run_3_layer_analysis():
|
|
| 437 |
print("⚠️ تم حفظ سجل تدقيق جزئي بعد الفشل.")
|
| 438 |
except Exception as audit_fail_error: print(f"❌ فشل حفظ سجل التدقيق أثناء معالجة خطأ آخر: {audit_fail_error}")
|
| 439 |
return None
|
| 440 |
-
# 🔴 --- نهاية التعديل الجوهري --- 🔴
|
| 441 |
|
| 442 |
-
# (بقية الملف: re_analyze_open_trade_async, run_bot_cycle_async, lifespan, endpoints, cleanup, etc. كما هي)
|
| 443 |
-
# ...
|
| 444 |
async def re_analyze_open_trade_async(trade_data):
|
| 445 |
"""إعادة تحليل الصفقة المفتوحة"""
|
| 446 |
symbol = trade_data.get('symbol')
|
|
@@ -450,21 +427,31 @@ async def re_analyze_open_trade_async(trade_data):
|
|
| 450 |
ohlcv_data_list = []
|
| 451 |
temp_queue = asyncio.Queue()
|
| 452 |
await data_manager_global.stream_ohlcv_data([symbol], temp_queue)
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 457 |
if not ohlcv_data_list: print(f"⚠️ فشل جلب بيانات إعادة التحليل لـ {symbol}"); return None
|
| 458 |
ohlcv_data = ohlcv_data_list[0]
|
| 459 |
|
| 460 |
l1_data = await data_manager_global._get_detailed_symbol_data(symbol)
|
| 461 |
if l1_data: ohlcv_data.update(l1_data); ohlcv_data['reasons_for_candidacy'] = ['re-analysis']
|
| 462 |
|
| 463 |
-
# 🔴 جلب بيانات الحيتان بشكل منفصل لإعادة التحليل
|
| 464 |
re_analysis_whale_data = await data_manager_global.get_whale_data_for_symbol(symbol)
|
| 465 |
|
| 466 |
ml_processor = MLProcessor(market_context, data_manager_global, learning_engine_global)
|
| 467 |
-
# 🔴 تمرير بيانات الحيتان للمعالج
|
| 468 |
processed_data = await ml_processor.process_and_score_symbol_enhanced(ohlcv_data, {symbol: re_analysis_whale_data} if re_analysis_whale_data else {})
|
| 469 |
|
| 470 |
if not processed_data: return None
|
|
@@ -478,7 +465,8 @@ async def re_analyze_open_trade_async(trade_data):
|
|
| 478 |
await r2_service_global.save_system_logs_async({ "trade_reanalyzed": True, "symbol": symbol, "action": re_analysis_decision.get('action'), 'strategy': re_analysis_decision.get('strategy', 'GENERIC') })
|
| 479 |
return {"symbol": symbol, "decision": re_analysis_decision, "current_price": processed_data.get('current_price')}
|
| 480 |
else: return None
|
| 481 |
-
except Exception as error: await r2_service_global.save_system_logs_async({ "reanalysis_error": True, "symbol": symbol, "error": str(error) }); return None
|
|
|
|
| 482 |
|
| 483 |
async def run_bot_cycle_async():
|
| 484 |
"""دورة التداول الرئيسية"""
|
|
@@ -486,6 +474,8 @@ async def run_bot_cycle_async():
|
|
| 486 |
if not await state_manager.wait_for_initialization(): print("❌ الخدمات غير مهيأة بالكامل - تخطي الدورة"); return
|
| 487 |
print("🔄 بدء دورة التداول..."); await r2_service_global.save_system_logs_async({"cycle_started": True})
|
| 488 |
if not r2_service_global.acquire_lock(): print("❌ فشل الحصول على القفل - تخطي الدورة"); return
|
|
|
|
|
|
|
| 489 |
try:
|
| 490 |
open_trades = await trade_manager_global.get_open_trades(); print(f"📋 الصفقات المفتوحة: {len(open_trades)}")
|
| 491 |
should_look_for_new_trade = len(open_trades) == 0
|
|
@@ -494,10 +484,29 @@ async def run_bot_cycle_async():
|
|
| 494 |
trades_to_reanalyze = [t for t in open_trades if now >= datetime.fromisoformat(t.get('expected_target_time', now.isoformat()))]
|
| 495 |
if trades_to_reanalyze:
|
| 496 |
print(f"🔄 إعادة تحليل {len(trades_to_reanalyze)} صفقة")
|
| 497 |
-
for
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 501 |
if should_look_for_new_trade:
|
| 502 |
portfolio_state = await r2_service_global.get_portfolio_state_async(); current_capital = portfolio_state.get("current_capital_usd", 0)
|
| 503 |
if current_capital > 1:
|
|
@@ -506,10 +515,18 @@ async def run_bot_cycle_async():
|
|
| 506 |
if best_opportunity: print(f"✅ فتح صفقة جديدة: {best_opportunity['symbol']}"); await trade_manager_global.open_trade( best_opportunity['symbol'], best_opportunity['decision'], best_opportunity['current_price'])
|
| 507 |
else: print("❌ لم يتم العثور على فرص تداول مناسبة")
|
| 508 |
else: print("❌ رأس المال غير كافي لفتح صفقات جديدة")
|
|
|
|
|
|
|
|
|
|
| 509 |
finally:
|
| 510 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 511 |
except Exception as error:
|
| 512 |
-
print(f"❌ Unhandled error in main cycle: {error}");
|
|
|
|
| 513 |
if r2_service_global and r2_service_global.lock_acquired: r2_service_global.release_lock() # Ensure lock release on error
|
| 514 |
|
| 515 |
@asynccontextmanager
|
|
@@ -524,9 +541,20 @@ async def lifespan(application: FastAPI):
|
|
| 524 |
await r2_service_global.save_system_logs_async({"application_started": True})
|
| 525 |
print("🎯 التطبيق جاهز للعمل - نظام الطبقات 3 فعال (فصل الحيتان)")
|
| 526 |
yield
|
| 527 |
-
except Exception as error:
|
| 528 |
-
|
| 529 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 530 |
|
| 531 |
application = FastAPI(lifespan=lifespan, title="AI Trading Bot", description="نظام تداول ذكي بثلاث طبقات تحليلية (فصل الحيتان)", version="3.2.0")
|
| 532 |
|
|
|
|
| 1 |
+
# app.py (محدث بإصلاح IndentationError)
|
| 2 |
import os
|
| 3 |
import traceback
|
| 4 |
import signal
|
|
|
|
| 26 |
print(f"❌ خطأ في استيراد الوحدات: {e}")
|
| 27 |
sys.exit(1)
|
| 28 |
|
| 29 |
+
# المتغيرات العالمية
|
|
|
|
| 30 |
r2_service_global = None
|
| 31 |
data_manager_global = None
|
| 32 |
llm_service_global = None
|
|
|
|
| 67 |
|
| 68 |
state_manager = StateManager()
|
| 69 |
|
|
|
|
|
|
|
| 70 |
async def initialize_services():
|
| 71 |
"""تهيئة جميع الخدمات بشكل منفصل"""
|
| 72 |
global r2_service_global, data_manager_global, llm_service_global
|
|
|
|
| 113 |
except Exception as e: print(f"❌ فشل تشغيل مراقبة السوق: {e}")
|
| 114 |
|
| 115 |
|
|
|
|
|
|
|
| 116 |
async def process_batch_parallel(batch, ml_processor, batch_num, total_batches, preloaded_whale_data):
|
| 117 |
"""
|
| 118 |
(معدلة) معالجة دفعة من الرموز بشكل متوازي وإرجاع نتائج مفصلة
|
|
|
|
| 168 |
return {'success': [], 'low_score': [], 'failures': []}
|
| 169 |
|
| 170 |
|
|
|
|
|
|
|
| 171 |
async def run_3_layer_analysis():
|
| 172 |
"""
|
| 173 |
(معدلة) تشغيل النظام الطبقي (مع فصل جلب بيانات الحيتان)
|
|
|
|
| 203 |
|
| 204 |
# مهمة جلب بيانات الحيتان
|
| 205 |
async def fetch_whale_data_task(symbols, results_dict):
|
| 206 |
+
WHALE_FETCH_CONCURRENCY = 3
|
|
|
|
| 207 |
semaphore = asyncio.Semaphore(WHALE_FETCH_CONCURRENCY)
|
| 208 |
tasks = []
|
| 209 |
|
|
|
|
| 213 |
data = await data_manager_global.get_whale_data_for_symbol(symbol)
|
| 214 |
if data:
|
| 215 |
results_dict[symbol] = data
|
|
|
|
|
|
|
| 216 |
except Exception as e:
|
| 217 |
print(f" ❌ [Whale Fetch] فشل جلب بيانات الحيتان لـ {symbol}: {e}")
|
| 218 |
+
results_dict[symbol] = {'data_available': False, 'error': str(e)}
|
| 219 |
|
| 220 |
for symbol in symbols:
|
| 221 |
tasks.append(asyncio.create_task(get_data_with_semaphore(symbol)))
|
| 222 |
|
| 223 |
+
await asyncio.gather(*tasks)
|
| 224 |
|
| 225 |
+
# تشغيل مهمة جلب الحيتان في الخلفية
|
| 226 |
whale_fetcher_task = asyncio.create_task(fetch_whale_data_task(layer1_symbols, preloaded_whale_data_dict))
|
| 227 |
print(" ⏳ مهمة جلب بيانات الحيتان تعمل في الخلفية...")
|
| 228 |
# 🔴 --- نهاية الطبقة 1.5 --- 🔴
|
|
|
|
| 291 |
|
| 292 |
# 9. تجميع النتائج (كما كان، لكن بيانات الحيتان موجودة الآن)
|
| 293 |
print("🔄 تجميع جميع النتائج...")
|
|
|
|
| 294 |
for batch_result in ml_results_list:
|
| 295 |
for success_item in batch_result['success']:
|
| 296 |
symbol = success_item['symbol']
|
|
|
|
| 298 |
if l1_data:
|
| 299 |
success_item['reasons_for_candidacy'] = l1_data.get('reasons', [])
|
| 300 |
success_item['layer1_score'] = l1_data.get('layer1_score', 0)
|
|
|
|
| 301 |
if symbol in preloaded_whale_data_dict:
|
| 302 |
success_item['whale_data'] = preloaded_whale_data_dict[symbol]
|
| 303 |
+
elif 'whale_data' not in success_item:
|
| 304 |
success_item['whale_data'] = {'data_available': False, 'reason': 'Failed during preload'}
|
| 305 |
layer2_candidates.append(success_item)
|
| 306 |
all_low_score_candidates.extend(batch_result['low_score'])
|
| 307 |
all_failed_candidates.extend(batch_result['failures'])
|
| 308 |
|
|
|
|
| 309 |
print(f"✅ اكتمل التحليل المتقدم: {len(layer2_candidates)} نجاح (عالي) | {len(all_low_score_candidates)} نجاح (منخفض) | {len(all_failed_candidates)} فشل")
|
| 310 |
|
| 311 |
if not layer2_candidates: print("❌ لم يتم العثور على مرشحين في الطبقة 2")
|
|
|
|
| 317 |
print(f"🎯 تم اختيار {len(final_layer2_candidates)} عملة للطبقة 3 (الأقوى فقط)")
|
| 318 |
await r2_service_global.save_candidates_async(final_layer2_candidates)
|
| 319 |
print("\n🏆 أفضل 10 عملات من الطبقة 2:")
|
|
|
|
| 320 |
for i, candidate in enumerate(final_layer2_candidates):
|
| 321 |
score = candidate.get('enhanced_final_score', 0)
|
| 322 |
strategy = candidate.get('target_strategy', 'GENERIC')
|
|
|
|
| 334 |
print(f" 🐋 حيتان: {signal.get('action', 'HOLD')} (ثقة: {signal.get('confidence', 0):.2f}){' ⚠️' if signal.get('critical_alert') else ''}")
|
| 335 |
elif whale_data and whale_data.get('error'):
|
| 336 |
print(f" 🐋 حيتان: خطأ ({whale_data.get('error')[:50]}...)")
|
|
|
|
|
|
|
|
|
|
| 337 |
|
| 338 |
# 11. الطبقة 3: التحليل بالنموذج الضخم (كما كان)
|
| 339 |
print("\n🧠 الطبقة 3: التحليل بالنموذج الضخم (LLMService)...")
|
|
|
|
| 340 |
for candidate in final_layer2_candidates:
|
| 341 |
try:
|
| 342 |
symbol = candidate['symbol']
|
|
|
|
| 365 |
print(f" ⚠️ {symbol}: لا يوجد قرار تداول من النموذج الضخم ({action})")
|
| 366 |
except Exception as e: print(f"❌ خطأ في تحليل النموذج الضخم لـ {candidate.get('symbol')}: {e}"); continue
|
| 367 |
|
|
|
|
| 368 |
if final_opportunities:
|
| 369 |
final_opportunities.sort(key=lambda x: (x['llm_confidence'] + x['enhanced_score']) / 2, reverse=True)
|
| 370 |
print(f"\n🏆 النظام الطبقي اكتمل: {len(final_opportunities)} فرصة تداول")
|
|
|
|
| 373 |
|
| 374 |
# 12. سجل التدقيق (كما كان)
|
| 375 |
try:
|
|
|
|
| 376 |
top_10_detailed_summary = []
|
| 377 |
for c in final_layer2_candidates:
|
| 378 |
whale_summary = "Not Available"
|
|
|
|
| 417 |
print("⚠️ تم حفظ سجل تدقيق جزئي بعد الفشل.")
|
| 418 |
except Exception as audit_fail_error: print(f"❌ فشل حفظ سجل التدقيق أثناء معالجة خطأ آخر: {audit_fail_error}")
|
| 419 |
return None
|
|
|
|
| 420 |
|
|
|
|
|
|
|
| 421 |
async def re_analyze_open_trade_async(trade_data):
|
| 422 |
"""إعادة تحليل الصفقة المفتوحة"""
|
| 423 |
symbol = trade_data.get('symbol')
|
|
|
|
| 427 |
ohlcv_data_list = []
|
| 428 |
temp_queue = asyncio.Queue()
|
| 429 |
await data_manager_global.stream_ohlcv_data([symbol], temp_queue)
|
| 430 |
+
# Correctly drain the queue
|
| 431 |
+
while True:
|
| 432 |
+
try:
|
| 433 |
+
batch = await asyncio.wait_for(temp_queue.get(), timeout=1.0) # Add timeout
|
| 434 |
+
if batch is None: # Check for sentinel
|
| 435 |
+
temp_queue.task_done()
|
| 436 |
+
break
|
| 437 |
+
ohlcv_data_list.extend(batch)
|
| 438 |
+
temp_queue.task_done()
|
| 439 |
+
except asyncio.TimeoutError:
|
| 440 |
+
# Queue might be empty or producer finished without sentinel
|
| 441 |
+
if temp_queue.empty(): break # Exit if empty after timeout
|
| 442 |
+
except Exception as q_err:
|
| 443 |
+
print(f"Error draining queue for re-analysis: {q_err}")
|
| 444 |
+
break # Exit on other errors
|
| 445 |
+
|
| 446 |
if not ohlcv_data_list: print(f"⚠️ فشل جلب بيانات إعادة التحليل لـ {symbol}"); return None
|
| 447 |
ohlcv_data = ohlcv_data_list[0]
|
| 448 |
|
| 449 |
l1_data = await data_manager_global._get_detailed_symbol_data(symbol)
|
| 450 |
if l1_data: ohlcv_data.update(l1_data); ohlcv_data['reasons_for_candidacy'] = ['re-analysis']
|
| 451 |
|
|
|
|
| 452 |
re_analysis_whale_data = await data_manager_global.get_whale_data_for_symbol(symbol)
|
| 453 |
|
| 454 |
ml_processor = MLProcessor(market_context, data_manager_global, learning_engine_global)
|
|
|
|
| 455 |
processed_data = await ml_processor.process_and_score_symbol_enhanced(ohlcv_data, {symbol: re_analysis_whale_data} if re_analysis_whale_data else {})
|
| 456 |
|
| 457 |
if not processed_data: return None
|
|
|
|
| 465 |
await r2_service_global.save_system_logs_async({ "trade_reanalyzed": True, "symbol": symbol, "action": re_analysis_decision.get('action'), 'strategy': re_analysis_decision.get('strategy', 'GENERIC') })
|
| 466 |
return {"symbol": symbol, "decision": re_analysis_decision, "current_price": processed_data.get('current_price')}
|
| 467 |
else: return None
|
| 468 |
+
except Exception as error: await r2_service_global.save_system_logs_async({ "reanalysis_error": True, "symbol": symbol, "error": str(error) }); print(f"❌ Error in re_analyze_open_trade_async for {symbol}: {error}"); traceback.print_exc(); return None
|
| 469 |
+
|
| 470 |
|
| 471 |
async def run_bot_cycle_async():
|
| 472 |
"""دورة التداول الرئيسية"""
|
|
|
|
| 474 |
if not await state_manager.wait_for_initialization(): print("❌ الخدمات غير مهيأة بالكامل - تخطي الدورة"); return
|
| 475 |
print("🔄 بدء دورة التداول..."); await r2_service_global.save_system_logs_async({"cycle_started": True})
|
| 476 |
if not r2_service_global.acquire_lock(): print("❌ فشل الحصول على القفل - تخطي الدورة"); return
|
| 477 |
+
|
| 478 |
+
open_trades = [] # Define open_trades before try block
|
| 479 |
try:
|
| 480 |
open_trades = await trade_manager_global.get_open_trades(); print(f"📋 الصفقات المفتوحة: {len(open_trades)}")
|
| 481 |
should_look_for_new_trade = len(open_trades) == 0
|
|
|
|
| 484 |
trades_to_reanalyze = [t for t in open_trades if now >= datetime.fromisoformat(t.get('expected_target_time', now.isoformat()))]
|
| 485 |
if trades_to_reanalyze:
|
| 486 |
print(f"🔄 إعادة تحليل {len(trades_to_reanalyze)} صفقة")
|
| 487 |
+
# Use gather for concurrent re-analysis, but process results sequentially
|
| 488 |
+
reanalysis_results = await asyncio.gather(*[re_analyze_open_trade_async(trade) for trade in trades_to_reanalyze], return_exceptions=True)
|
| 489 |
+
|
| 490 |
+
for i, result in enumerate(reanalysis_results):
|
| 491 |
+
trade = trades_to_reanalyze[i] # Get corresponding trade
|
| 492 |
+
if isinstance(result, Exception):
|
| 493 |
+
print(f" ❌ فشل إعادة تحليل {trade.get('symbol')}: {result}")
|
| 494 |
+
elif result and result['decision'].get('action') == "CLOSE_TRADE":
|
| 495 |
+
print(f" ✅ إغلاق {trade.get('symbol')} بناءً على إعادة التحليل.")
|
| 496 |
+
await trade_manager_global.close_trade(trade, result['current_price'], 'CLOSED_BY_REANALYSIS');
|
| 497 |
+
should_look_for_new_trade = True # Now we can look for a new trade
|
| 498 |
+
elif result and result['decision'].get('action') == "UPDATE_TRADE":
|
| 499 |
+
print(f" ✅ تحديث {trade.get('symbol')} بناءً على إعادة التحليل.")
|
| 500 |
+
await trade_manager_global.update_trade(trade, result['decision'])
|
| 501 |
+
elif result: # Handle HOLD case (log or do nothing)
|
| 502 |
+
print(f" ℹ️ الاحتفاظ بـ {trade.get('symbol')} بناءً على إعادة التحليل.")
|
| 503 |
+
else: # Handle case where re-analysis returned None without error
|
| 504 |
+
print(f" ⚠️ إعادة تحليل {trade.get('symbol')} لم تنتج قرارًا.")
|
| 505 |
+
|
| 506 |
+
# Recalculate should_look_for_new_trade after potential closures
|
| 507 |
+
current_open_trades_count = len(await trade_manager_global.get_open_trades())
|
| 508 |
+
should_look_for_new_trade = current_open_trades_count == 0
|
| 509 |
+
|
| 510 |
if should_look_for_new_trade:
|
| 511 |
portfolio_state = await r2_service_global.get_portfolio_state_async(); current_capital = portfolio_state.get("current_capital_usd", 0)
|
| 512 |
if current_capital > 1:
|
|
|
|
| 515 |
if best_opportunity: print(f"✅ فتح صفقة جديدة: {best_opportunity['symbol']}"); await trade_manager_global.open_trade( best_opportunity['symbol'], best_opportunity['decision'], best_opportunity['current_price'])
|
| 516 |
else: print("❌ لم يتم العثور على فرص تداول مناسبة")
|
| 517 |
else: print("❌ رأس المال غير كافي لفتح صفقات جديدة")
|
| 518 |
+
else:
|
| 519 |
+
print("ℹ️ يوجد صفقة مفتوحة بالفعل، تخطي البحث عن صفقة جديدة.")
|
| 520 |
+
|
| 521 |
finally:
|
| 522 |
+
if r2_service_global.lock_acquired: # Check if lock was acquired before releasing
|
| 523 |
+
r2_service_global.release_lock()
|
| 524 |
+
await r2_service_global.save_system_logs_async({ "cycle_completed": True, "open_trades": len(open_trades)}) # Use initial count for log consistency
|
| 525 |
+
print("✅ اكتملت دورة التداول")
|
| 526 |
+
|
| 527 |
except Exception as error:
|
| 528 |
+
print(f"❌ Unhandled error in main cycle: {error}"); traceback.print_exc() # Print traceback
|
| 529 |
+
await r2_service_global.save_system_logs_async({ "cycle_error": True, "error": str(error) });
|
| 530 |
if r2_service_global and r2_service_global.lock_acquired: r2_service_global.release_lock() # Ensure lock release on error
|
| 531 |
|
| 532 |
@asynccontextmanager
|
|
|
|
| 541 |
await r2_service_global.save_system_logs_async({"application_started": True})
|
| 542 |
print("🎯 التطبيق جاهز للعمل - نظام الطبقات 3 فعال (فصل الحيتان)")
|
| 543 |
yield
|
| 544 |
+
except Exception as error:
|
| 545 |
+
print(f"❌ Application startup failed: {error}");
|
| 546 |
+
traceback.print_exc() # Print traceback here as well
|
| 547 |
+
if r2_service_global:
|
| 548 |
+
await r2_service_global.save_system_logs_async({
|
| 549 |
+
"application_startup_failed": True,
|
| 550 |
+
"error": str(error)
|
| 551 |
+
})
|
| 552 |
+
# 🔴 --- الإصلاح: فصل raise إلى سطر جديد --- 🔴
|
| 553 |
+
raise # Correct indentation
|
| 554 |
+
# 🔴 --- نهاية الإصلاح --- 🔴
|
| 555 |
+
finally:
|
| 556 |
+
await cleanup_on_shutdown()
|
| 557 |
+
|
| 558 |
|
| 559 |
application = FastAPI(lifespan=lifespan, title="AI Trading Bot", description="نظام تداول ذكي بثلاث طبقات تحليلية (فصل الحيتان)", version="3.2.0")
|
| 560 |
|