Update app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# app.py (
|
| 2 |
import os
|
| 3 |
import traceback
|
| 4 |
import signal
|
|
@@ -10,59 +10,40 @@ import time
|
|
| 10 |
from contextlib import asynccontextmanager
|
| 11 |
from fastapi import FastAPI, HTTPException
|
| 12 |
from datetime import datetime
|
| 13 |
-
from typing import List, Dict, Any
|
| 14 |
|
|
|
|
| 15 |
try:
|
| 16 |
from r2 import R2Service
|
| 17 |
from LLM import LLMService
|
| 18 |
-
from data_manager import DataManager
|
| 19 |
from ml_engine.processor import MLProcessor
|
| 20 |
from learning_hub.hub_manager import LearningHubManager
|
| 21 |
-
from sentiment_news import SentimentAnalyzer, NewsFetcher # (V8.1) استيراد NewsFetcher
|
| 22 |
from trade_manager import TradeManager
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
from helpers import safe_float_conversion, validate_candidate_data_enhanced
|
| 26 |
except ImportError as e:
|
| 27 |
-
print(f"❌ خطأ في استيراد الوحدات: {e}")
|
| 28 |
-
if "ccxt.async_support" in str(e) or "ccxtpro" in str(e):
|
| 29 |
-
print("🚨 خطأ فادح: تأكد من أن 'ccxt' (الإصدار 4+) مثبت وأن 'ccxt-pro' محذوف.")
|
| 30 |
sys.exit(1)
|
| 31 |
|
| 32 |
-
#
|
| 33 |
-
try:
|
| 34 |
-
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
|
| 35 |
-
VADER_ANALYZER = SentimentIntensityAnalyzer()
|
| 36 |
-
print("✅ تم تحميل VADER Sentiment Analyzer بنجاح")
|
| 37 |
-
except ImportError:
|
| 38 |
-
print("❌❌ فشل استيراد VADER. درجة الأخبار ستكون معطلة. ❌❌")
|
| 39 |
-
print(" قم بتثبيتها باستخدام: pip install vaderSentiment")
|
| 40 |
-
VADER_ANALYZER = None
|
| 41 |
-
|
| 42 |
-
# المتغيرات العالمية
|
| 43 |
r2_service_global = None
|
| 44 |
data_manager_global = None
|
| 45 |
llm_service_global = None
|
| 46 |
learning_hub_global = None
|
| 47 |
trade_manager_global = None
|
| 48 |
-
|
| 49 |
-
symbol_whale_monitor_global = None
|
| 50 |
-
news_fetcher_global = None # (V8.1) إضافة NewsFetcher
|
| 51 |
|
| 52 |
MARKET_STATE_OK = True
|
| 53 |
|
| 54 |
-
|
| 55 |
class StateManager:
|
| 56 |
-
# ... (لا تغيير في هذا الكلاس) ...
|
| 57 |
def __init__(self):
|
| 58 |
self.market_analysis_lock = asyncio.Lock()
|
| 59 |
self.trade_analysis_lock = asyncio.Lock()
|
| 60 |
self.initialization_complete = False
|
| 61 |
self.initialization_error = None
|
| 62 |
self.services_initialized = {
|
| 63 |
-
'
|
| 64 |
-
'learning_hub': False, 'trade_manager': False, 'sentiment_analyzer': False,
|
| 65 |
-
'symbol_whale_monitor': False, 'news_fetcher': False # (V8.1)
|
| 66 |
}
|
| 67 |
|
| 68 |
async def wait_for_initialization(self, timeout=60):
|
|
@@ -77,7 +58,7 @@ class StateManager:
|
|
| 77 |
self.services_initialized[service_name] = True
|
| 78 |
if all(self.services_initialized.values()):
|
| 79 |
self.initialization_complete = True
|
| 80 |
-
print("🎯 جميع الخدمات مهيأة
|
| 81 |
|
| 82 |
def set_initialization_error(self, error):
|
| 83 |
self.initialization_error = error
|
|
@@ -86,779 +67,252 @@ class StateManager:
|
|
| 86 |
state_manager = StateManager()
|
| 87 |
|
| 88 |
async def initialize_services():
|
| 89 |
-
"""تهيئة
|
| 90 |
global r2_service_global, data_manager_global, llm_service_global
|
| 91 |
-
global learning_hub_global, trade_manager_global,
|
| 92 |
-
|
| 93 |
try:
|
| 94 |
-
|
| 95 |
-
print("🚀 بدء تهيئة الخدمات (بنية Sentry الجديدة V5.9)...")
|
| 96 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 97 |
-
print(" 🔄 تهيئة R2Service..."); r2_service_global = R2Service(); state_manager.set_service_initialized('r2_service'); print(" ✅ R2Service مهيأة")
|
| 98 |
-
print(" 🔄 جلب قاعدة بيانات العقود..."); contracts_database = await r2_service_global.load_contracts_db_async(); print(f" ✅ تم تحميل {len(contracts_database)} عقد")
|
| 99 |
-
|
| 100 |
-
print(" 🔄 تهيئة مراقب الحيتان (Layer 1 Data)...");
|
| 101 |
-
try:
|
| 102 |
-
from whale_monitor.core import EnhancedWhaleMonitor
|
| 103 |
-
|
| 104 |
-
symbol_whale_monitor_global = EnhancedWhaleMonitor(contracts_database, r2_service_global)
|
| 105 |
-
state_manager.set_service_initialized('symbol_whale_monitor'); print(" ✅ مراقب الحيتان مهيأ")
|
| 106 |
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
symbol_whale_monitor_global = None
|
| 111 |
-
state_manager.set_service_initialized('symbol_whale_monitor');
|
| 112 |
-
print(" ℹ️ مراقبة الحيتان معطلة. استمرار التهيئة...")
|
| 113 |
-
|
| 114 |
-
# (V8-MODIFICATION) تمرير r2_service_global إلى DataManager
|
| 115 |
-
print(" 🔄 تهيئة DataManager (Layer 1 Data)...");
|
| 116 |
-
data_manager_global = DataManager(contracts_database, symbol_whale_monitor_global, r2_service_global)
|
| 117 |
-
await data_manager_global.initialize();
|
| 118 |
-
state_manager.set_service_initialized('data_manager');
|
| 119 |
-
print(" ✅ DataManager مهيأ (ومحرك الأنماط V8 مُحمّل)")
|
| 120 |
-
|
| 121 |
-
# (ربط DataManager بـ WhaleMonitor لحل الاعتمادية الدائرية للسعر)
|
| 122 |
-
if symbol_whale_monitor_global:
|
| 123 |
-
symbol_whale_monitor_global.data_manager = data_manager_global
|
| 124 |
-
print(" ✅ [Whale Link] تم ربط DataManager بـ WhaleMonitor.")
|
| 125 |
-
|
| 126 |
-
print(" 🔄 تهيئة LLMService (Layer 1 Brain)...");
|
| 127 |
-
llm_service_global = LLMService();
|
| 128 |
-
llm_service_global.r2_service = r2_service_global;
|
| 129 |
-
|
| 130 |
-
print(" 🔄 تهيئة محلل المشاعر (Layer 1 Data)...");
|
| 131 |
-
sentiment_analyzer_global = SentimentAnalyzer(data_manager_global);
|
| 132 |
-
state_manager.set_service_initialized('sentiment_analyzer');
|
| 133 |
-
print(" ✅ محلل المشاعر مهيأ")
|
| 134 |
|
| 135 |
-
# (
|
| 136 |
-
|
| 137 |
-
news_fetcher_global = NewsFetcher()
|
| 138 |
-
state_manager.set_service_initialized('news_fetcher');
|
| 139 |
-
print(" ✅ NewsFetcher (V8.1) مهيأ")
|
| 140 |
|
| 141 |
-
#
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
#
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
llm_service=llm_service_global,
|
| 152 |
-
data_manager=data_manager_global
|
| 153 |
-
)
|
| 154 |
await learning_hub_global.initialize()
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
|
| 162 |
-
print(" 🔄 تهيئة مدير الصفقات (Layer 2 Sentry + Layer 3 Executor)...");
|
| 163 |
-
|
| 164 |
-
# (تمرير دالة الدورة كـ "رد نداء" ليتم استدعاؤها بعد إغلاق الصفقة)
|
| 165 |
trade_manager_global = TradeManager(
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
data_manager=data_manager_global,
|
| 169 |
-
state_manager=state_manager,
|
| 170 |
-
callback_on_close=run_bot_cycle_async
|
| 171 |
)
|
| 172 |
-
|
| 173 |
await trade_manager_global.initialize_sentry_exchanges()
|
| 174 |
-
state_manager.set_service_initialized('
|
| 175 |
-
print(" ✅ مدير الصفقات (Sentry/Executor) مهيأ")
|
| 176 |
|
| 177 |
-
|
| 178 |
-
except Exception as e:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
|
| 180 |
async def monitor_market_async():
|
| 181 |
-
|
| 182 |
-
global data_manager_global, sentiment_analyzer_global, MARKET_STATE_OK
|
| 183 |
try:
|
| 184 |
-
if not await state_manager.wait_for_initialization():
|
| 185 |
while True:
|
| 186 |
try:
|
| 187 |
async with state_manager.market_analysis_lock:
|
| 188 |
-
|
|
|
|
| 189 |
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
if bitcoin_sentiment == 'BEARISH' and (fear_greed_index is not None and fear_greed_index < 30): should_halt_trading, halt_reason = True, "ظروف سوق هابطة"
|
| 196 |
-
|
| 197 |
-
if should_halt_trading:
|
| 198 |
-
MARKET_STATE_OK = False;
|
| 199 |
-
await r2_service_global.save_system_logs_async({"market_halt": True, "reason": halt_reason})
|
| 200 |
else:
|
| 201 |
-
if not MARKET_STATE_OK:
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
await asyncio.sleep(60)
|
| 209 |
-
except Exception as e: print(f"❌ فشل تشغيل مراقبة السوق: {e}")
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
async def run_periodic_distillation():
|
| 213 |
-
# ... (لا تغيير في هذه الدالة) ...
|
| 214 |
-
print("background task: Periodic Distillation (Curator) scheduled.")
|
| 215 |
-
await asyncio.sleep(300)
|
| 216 |
-
|
| 217 |
-
while True:
|
| 218 |
-
try:
|
| 219 |
-
if not await state_manager.wait_for_initialization():
|
| 220 |
await asyncio.sleep(60)
|
| 221 |
-
|
| 222 |
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
|
|
|
|
|
|
| 226 |
|
| 227 |
-
|
| 228 |
-
print(f"❌ [Scheduler] Error in periodic distillation task: {e}")
|
| 229 |
-
traceback.print_exc()
|
| 230 |
-
await asyncio.sleep(60 * 60)
|
| 231 |
-
|
| 232 |
-
async def process_batch_parallel(batch, ml_processor, batch_num, total_batches, preloaded_whale_data):
|
| 233 |
-
# ... (لا تغيير في هذه الدالة) ...
|
| 234 |
try:
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
if result_list:
|
| 253 |
-
result = result_list[0]
|
| 254 |
-
if isinstance(result, dict):
|
| 255 |
-
if result.get('enhanced_final_score', 0) > 0.4:
|
| 256 |
-
successful_results.append(result)
|
| 257 |
-
else:
|
| 258 |
-
low_score_results.append(result)
|
| 259 |
-
else:
|
| 260 |
-
failed_results.append({"symbol": symbol, "error": f"ML processor returned invalid type: {type(result)}"})
|
| 261 |
-
else:
|
| 262 |
-
failed_results.append({"symbol": symbol, "error": "ML processing returned None or empty list"})
|
| 263 |
-
|
| 264 |
-
return {'success': successful_results, 'low_score': low_score_results, 'failures': failed_results}
|
| 265 |
-
|
| 266 |
-
except Exception as error:
|
| 267 |
-
print(f"❌ [Consumer] Error processing batch {batch_num}: {error}")
|
| 268 |
-
return {'success': [], 'low_score': [], 'failures': []}
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
async def run_3_layer_analysis_explorer() -> List[Dict[str, Any]]:
|
| 272 |
-
"""
|
| 273 |
-
(معدل V8.7) - إصلاح العائد المتوقع (Expected Return)
|
| 274 |
-
"""
|
| 275 |
-
layer1_candidates = []
|
| 276 |
-
layer2_candidates = []
|
| 277 |
-
final_layer2_candidates = []
|
| 278 |
-
watchlist_candidates = []
|
| 279 |
-
|
| 280 |
-
preloaded_whale_data_dict = {}
|
| 281 |
-
|
| 282 |
-
try:
|
| 283 |
-
print("🎯 Starting Explorer Analysis (Layer 1)...")
|
| 284 |
-
|
| 285 |
-
if not await state_manager.wait_for_initialization():
|
| 286 |
-
print("❌ Services not fully initialized (Explorer)"); return []
|
| 287 |
-
|
| 288 |
-
# (V8.1) التأكد من تهيئة VADER و NewsFetcher
|
| 289 |
-
if not VADER_ANALYZER or not news_fetcher_global:
|
| 290 |
-
print("❌ VADER or NewsFetcher not initialized! News analysis will be skipped.")
|
| 291 |
-
|
| 292 |
-
print("\n🔍 Layer 1.1: Rapid Screening (data_manager V7.3)...")
|
| 293 |
-
layer1_candidates = await data_manager_global.layer1_rapid_screening()
|
| 294 |
-
if not layer1_candidates: print("❌ No candidates found in Layer 1.1"); return []
|
| 295 |
-
print(f"✅ Selected {len(layer1_candidates)} symbols for Layer 1.2")
|
| 296 |
-
|
| 297 |
-
print(f"\n📊 Layer 1.2: Fetching OHLCV data for {len(layer1_candidates)} symbols (Streaming)...")
|
| 298 |
-
DATA_QUEUE_MAX_SIZE = 2
|
| 299 |
-
ohlcv_data_queue = asyncio.Queue(maxsize=DATA_QUEUE_MAX_SIZE)
|
| 300 |
-
ml_results_list = []
|
| 301 |
-
market_context = await data_manager_global.get_market_context_async()
|
| 302 |
-
|
| 303 |
-
ml_processor = MLProcessor(market_context, data_manager_global, learning_hub_global)
|
| 304 |
-
|
| 305 |
-
batch_size = 15
|
| 306 |
-
total_batches = (len(layer1_candidates) + batch_size - 1) // batch_size
|
| 307 |
-
|
| 308 |
-
async def ml_consumer_task(queue: asyncio.Queue, results_list: list, whale_data_store: dict):
|
| 309 |
-
# ... (لا تغيير في هذه الدالة الداخلية) ...
|
| 310 |
-
batch_num = 0
|
| 311 |
-
while True:
|
| 312 |
-
try:
|
| 313 |
-
batch_data = await queue.get()
|
| 314 |
-
if batch_data is None:
|
| 315 |
-
queue.task_done()
|
| 316 |
-
break
|
| 317 |
-
batch_num += 1
|
| 318 |
-
batch_results_dict = await process_batch_parallel(
|
| 319 |
-
batch_data, ml_processor, batch_num, total_batches, whale_data_store
|
| 320 |
-
)
|
| 321 |
-
results_list.append(batch_results_dict)
|
| 322 |
-
queue.task_done()
|
| 323 |
-
except Exception as e:
|
| 324 |
-
print(f"❌ [ML Consumer] Fatal Error: {e}");
|
| 325 |
-
traceback.print_exc();
|
| 326 |
-
queue.task_done()
|
| 327 |
-
|
| 328 |
-
consumer_task = asyncio.create_task(ml_consumer_task(ohlcv_data_queue, ml_results_list, preloaded_whale_data_dict))
|
| 329 |
|
| 330 |
-
|
|
|
|
|
|
|
|
|
|
| 331 |
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
for success_item in batch_result['success']:
|
| 339 |
-
symbol = success_item['symbol']
|
| 340 |
-
l1_data = success_item
|
| 341 |
-
|
| 342 |
-
if l1_data:
|
| 343 |
-
success_item['reasons_for_candidacy'] = l1_data.get('reasons_for_candidacy', [])
|
| 344 |
-
success_item['layer1_score'] = l1_data.get('layer1_score', 0)
|
| 345 |
-
|
| 346 |
-
success_item['whale_data'] = {'data_available': False, 'reason': 'Not fetched yet'}
|
| 347 |
-
# (V8.2) إضافة قيم افتراضية للأخبار
|
| 348 |
-
success_item['news_text'] = ""
|
| 349 |
-
success_item['news_score_raw'] = 0.0 # درجة VADER الخام
|
| 350 |
-
success_item['statistical_news_pnl'] = 0.0 # الدرجة المتعلمة
|
| 351 |
-
|
| 352 |
-
# (V8.3) تمرير البيانات التي يحتاجها تحليل الحيتان
|
| 353 |
-
original_l1_data = next((c for c in layer1_candidates if c['symbol'] == symbol), None)
|
| 354 |
-
if original_l1_data:
|
| 355 |
-
success_item['dollar_volume'] = original_l1_data.get('dollar_volume', 0.0)
|
| 356 |
-
|
| 357 |
-
layer2_candidates.append(success_item)
|
| 358 |
-
|
| 359 |
-
if not layer2_candidates: print("❌ No candidates found in Layer 1.3"); return []
|
| 360 |
-
|
| 361 |
-
layer2_candidates.sort(key=lambda x: x.get('enhanced_final_score', 0), reverse=True)
|
| 362 |
-
|
| 363 |
-
target_count = min(10, len(layer2_candidates))
|
| 364 |
-
|
| 365 |
-
final_layer2_candidates = layer2_candidates[:target_count]
|
| 366 |
-
|
| 367 |
-
print(f"\n🐋📰 Layer 1.4 (Optimized): Fetching Whale Data & News for top {len(final_layer2_candidates)} candidates...")
|
| 368 |
-
|
| 369 |
-
# (دالة مساعدة لجلب الحيتان)
|
| 370 |
-
async def get_whale_data_for_candidate(candidate):
|
| 371 |
-
symbol = candidate.get('symbol', 'UNKNOWN')
|
| 372 |
-
symbol_daily_volume = candidate.get('dollar_volume', 0.0)
|
| 373 |
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
daily_volume_usd=symbol_daily_volume
|
| 378 |
-
)
|
| 379 |
-
if data:
|
| 380 |
-
candidate['whale_data'] = data
|
| 381 |
-
else:
|
| 382 |
-
candidate['whale_data'] = {'data_available': False, 'reason': 'No data returned'}
|
| 383 |
-
except Exception as e:
|
| 384 |
-
print(f" ❌ [Whale Fetch] {symbol} - Error: {e}")
|
| 385 |
-
candidate['whale_data'] = {'data_available': False, 'error': str(e)}
|
| 386 |
-
|
| 387 |
-
# (دالة مساعدة لجلب الأخبار وتحليل VADER)
|
| 388 |
-
async def get_news_data_for_candidate(candidate):
|
| 389 |
-
symbol = candidate.get('symbol', 'UNKNOWN')
|
| 390 |
-
if not news_fetcher_global or not VADER_ANALYZER:
|
| 391 |
-
candidate['news_text'] = "News analysis disabled."
|
| 392 |
-
candidate['news_score_raw'] = 0.0
|
| 393 |
-
return
|
| 394 |
-
|
| 395 |
-
try:
|
| 396 |
-
news_text = await news_fetcher_global.get_news_for_symbol(symbol)
|
| 397 |
-
candidate['news_text'] = news_text
|
| 398 |
-
|
| 399 |
-
if "No specific news found" in news_text or not news_text:
|
| 400 |
-
candidate['news_score_raw'] = 0.0
|
| 401 |
-
else:
|
| 402 |
-
vader_score = VADER_ANALYZER.polarity_scores(news_text)
|
| 403 |
-
candidate['news_score_raw'] = vader_score.get('compound', 0.0)
|
| 404 |
-
|
| 405 |
-
except Exception as e:
|
| 406 |
-
print(f" ❌ [News Fetch] {symbol} - Error: {e}")
|
| 407 |
-
candidate['news_text'] = f"Error fetching news: {e}"
|
| 408 |
-
candidate['news_score_raw'] = 0.0
|
| 409 |
-
|
| 410 |
-
# (تنفيذ المهام بالتوازي)
|
| 411 |
-
tasks = []
|
| 412 |
-
for candidate in final_layer2_candidates:
|
| 413 |
-
tasks.append(asyncio.create_task(get_whale_data_for_candidate(candidate)))
|
| 414 |
-
tasks.append(asyncio.create_task(get_news_data_for_candidate(candidate)))
|
| 415 |
-
|
| 416 |
-
await asyncio.gather(*tasks)
|
| 417 |
-
print(" ✅ Whale data and News data fetched for top candidates.")
|
| 418 |
-
|
| 419 |
-
print(" 🔄 Re-calculating enhanced scores with new Whale & Statistical News data...")
|
| 420 |
-
for candidate in final_layer2_candidates:
|
| 421 |
-
try:
|
| 422 |
-
raw_vader_score = candidate.get('news_score_raw', 0.0)
|
| 423 |
-
|
| 424 |
-
if learning_hub_global:
|
| 425 |
-
statistical_pnl = await learning_hub_global.get_statistical_news_score(raw_vader_score)
|
| 426 |
-
candidate['statistical_news_pnl'] = statistical_pnl
|
| 427 |
-
else:
|
| 428 |
-
candidate['statistical_news_pnl'] = 0.0
|
| 429 |
-
|
| 430 |
-
new_score = ml_processor._calculate_enhanced_final_score(candidate)
|
| 431 |
-
candidate['enhanced_final_score'] = new_score
|
| 432 |
-
except Exception as e:
|
| 433 |
-
print(f" ❌ [Score Recalc] {candidate.get('symbol')} - Error: {e}")
|
| 434 |
-
|
| 435 |
-
final_layer2_candidates.sort(key=lambda x: x.get('enhanced_final_score', 0), reverse=True)
|
| 436 |
-
print(" ✅ Top scores updated (with Stat. News + Whale) and re-sorted.")
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
print(f"\n🔬 Layer 1.5: Running Advanced MC (GARCH+LGBM) on top {len(final_layer2_candidates)} candidates...")
|
| 440 |
-
advanced_mc_analyzer = ml_processor.monte_carlo_analyzer
|
| 441 |
-
updated_candidates_for_llm = []
|
| 442 |
-
for candidate in final_layer2_candidates:
|
| 443 |
-
symbol = candidate.get('symbol', 'UNKNOWN')
|
| 444 |
-
try:
|
| 445 |
-
advanced_mc_results = await advanced_mc_analyzer.generate_1h_distribution_advanced(
|
| 446 |
-
candidate.get('ohlcv')
|
| 447 |
-
)
|
| 448 |
-
if advanced_mc_results and advanced_mc_results.get('simulation_model') == 'Phase2_GARCH_LGBM':
|
| 449 |
-
|
| 450 |
-
# 🔴 --- START OF CHANGE (V8.7 - MC Expected Return Fix) --- 🔴
|
| 451 |
-
# (إصلاح: حساب العائد المتوقع يدوياً إذا كان مفقوداً)
|
| 452 |
-
if 'expected_return_pct' not in advanced_mc_results:
|
| 453 |
-
try:
|
| 454 |
-
mean_price = advanced_mc_results.get('distribution_summary', {}).get('mean_price', 0)
|
| 455 |
-
current_price = advanced_mc_results.get('current_price', 0)
|
| 456 |
-
if mean_price > 0 and current_price > 0:
|
| 457 |
-
expected_return_pct = (mean_price - current_price) / current_price
|
| 458 |
-
advanced_mc_results['expected_return_pct'] = expected_return_pct
|
| 459 |
-
print(f" [MC Patch] {symbol}: Calculated Expected Return: {expected_return_pct:+.2%}")
|
| 460 |
-
else:
|
| 461 |
-
advanced_mc_results['expected_return_pct'] = 0.0
|
| 462 |
-
except Exception:
|
| 463 |
-
advanced_mc_results['expected_return_pct'] = 0.0
|
| 464 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 465 |
-
|
| 466 |
-
candidate['monte_carlo_distribution'] = advanced_mc_results
|
| 467 |
-
candidate['monte_carlo_probability'] = advanced_mc_results.get('probability_of_gain', 0)
|
| 468 |
-
candidate['advanced_mc_run'] = True
|
| 469 |
-
else:
|
| 470 |
-
candidate['advanced_mc_run'] = False
|
| 471 |
-
updated_candidates_for_llm.append(candidate)
|
| 472 |
-
except Exception as e:
|
| 473 |
-
print(f" ❌ [Advanced MC] {symbol} - Error: {e}. Using Phase 1 results.")
|
| 474 |
-
candidate['advanced_mc_run'] = False
|
| 475 |
-
updated_candidates_for_llm.append(candidate)
|
| 476 |
-
|
| 477 |
-
print(" 🔄 Sanitizing final candidates for JSON serialization...")
|
| 478 |
-
sanitized_candidates = []
|
| 479 |
-
for cand in updated_candidates_for_llm:
|
| 480 |
-
sanitized_candidates.append(_sanitize_results_for_json(cand))
|
| 481 |
-
|
| 482 |
-
final_layer2_candidates = sanitized_candidates
|
| 483 |
-
|
| 484 |
-
await r2_service_global.save_candidates_async(final_layer2_candidates)
|
| 485 |
-
|
| 486 |
-
print("\n🧠 Layer 1.6: LLM Strategic Analysis (Explorer Brain)...")
|
| 487 |
-
top_5_for_llm = final_layer2_candidates[:5]
|
| 488 |
-
print(f" (Sending Top {len(top_5_for_llm)} candidates to LLM)")
|
| 489 |
-
|
| 490 |
-
for candidate in top_5_for_llm:
|
| 491 |
-
try:
|
| 492 |
-
symbol = candidate['symbol']
|
| 493 |
-
ohlcv_data = candidate.get('ohlcv');
|
| 494 |
-
if not ohlcv_data: continue
|
| 495 |
-
|
| 496 |
-
candidate['raw_ohlcv'] = ohlcv_data
|
| 497 |
-
total_candles = sum(len(data) for data in ohlcv_data.values()) if ohlcv_data else 0
|
| 498 |
-
if total_candles < 30: continue
|
| 499 |
-
|
| 500 |
-
candidate['sentiment_data'] = await data_manager_global.get_market_context_async()
|
| 501 |
-
|
| 502 |
-
llm_analysis = await llm_service_global.get_trading_decision(candidate)
|
| 503 |
-
|
| 504 |
-
if llm_analysis and llm_analysis.get('action') in ['WATCH']:
|
| 505 |
-
strategy_to_watch = llm_analysis.get('strategy_to_watch', 'GENERIC')
|
| 506 |
-
confidence = llm_analysis.get('confidence_level', 0)
|
| 507 |
-
|
| 508 |
-
watchlist_entry = {
|
| 509 |
-
'symbol': symbol,
|
| 510 |
-
'strategy_hint': strategy_to_watch,
|
| 511 |
-
'explorer_score': candidate.get('enhanced_final_score', 0),
|
| 512 |
-
'llm_confidence': confidence,
|
| 513 |
-
'analysis_timestamp': datetime.now().isoformat(),
|
| 514 |
-
'llm_decision_context': {
|
| 515 |
-
'decision': llm_analysis,
|
| 516 |
-
'full_candidate_data': candidate
|
| 517 |
-
}
|
| 518 |
-
}
|
| 519 |
-
watchlist_candidates.append(watchlist_entry)
|
| 520 |
-
print(f" ✅ {symbol}: Added to Sentry Watchlist (Strategy: {strategy_to_watch} | Conf: {confidence:.2f})")
|
| 521 |
-
else:
|
| 522 |
-
action = llm_analysis.get('action', 'NO_DECISION') if llm_analysis else 'NO_RESPONSE';
|
| 523 |
-
print(f" ⚠️ {symbol}: Not recommended by LLM for watching ({action})")
|
| 524 |
-
except Exception as e: print(f"❌ Error in LLM analysis for {candidate.get('symbol')}: {e}"); traceback.print_exc(); continue
|
| 525 |
-
|
| 526 |
-
if watchlist_candidates:
|
| 527 |
-
watchlist_candidates.sort(key=lambda x: (x['llm_confidence'] + x['explorer_score']) / 2, reverse=True)
|
| 528 |
-
|
| 529 |
-
if not watchlist_candidates:
|
| 530 |
-
print("❌ Explorer analysis complete: No suitable candidates for Sentry Watchlist.")
|
| 531 |
-
return []
|
| 532 |
-
|
| 533 |
-
top_watchlist = watchlist_candidates
|
| 534 |
-
|
| 535 |
-
print("📊 إنشاء سجل تدقيق لمحرك الأنماط V8...")
|
| 536 |
-
audit_log = {
|
| 537 |
-
"log_id": f"audit_{int(datetime.now().timestamp())}",
|
| 538 |
-
"timestamp": datetime.now().isoformat(),
|
| 539 |
-
"model_key": "lgbm_pattern_model_combined.pkl",
|
| 540 |
-
"scaler_key": "scaler_combined.pkl",
|
| 541 |
-
"model_accuracy": 0.5870,
|
| 542 |
-
"predictions": []
|
| 543 |
-
}
|
| 544 |
-
|
| 545 |
-
for candidate in final_layer2_candidates:
|
| 546 |
-
pattern_analysis = candidate.get('pattern_analysis', {})
|
| 547 |
-
audit_entry = {
|
| 548 |
-
"symbol": candidate.get('symbol', 'N/A'),
|
| 549 |
-
"timeframe": pattern_analysis.get('timeframe', 'N/A'),
|
| 550 |
-
"pattern_detected": pattern_analysis.get('pattern_detected', 'N/A'),
|
| 551 |
-
"confidence": pattern_analysis.get('pattern_confidence', 0),
|
| 552 |
-
"predicted_direction": pattern_analysis.get('predicted_direction', 'neutral'),
|
| 553 |
-
"error": "None"
|
| 554 |
-
}
|
| 555 |
-
audit_log["predictions"].append(audit_entry)
|
| 556 |
-
|
| 557 |
-
if r2_service_global:
|
| 558 |
-
await r2_service_global.save_analysis_audit_log_async(audit_log)
|
| 559 |
-
|
| 560 |
-
print(f"✅ Explorer analysis complete. Sending {len(top_watchlist)} candidates to Sentry.")
|
| 561 |
-
return top_watchlist
|
| 562 |
-
|
| 563 |
-
except Exception as error:
|
| 564 |
-
print(f"❌ Fatal error in Explorer (Layer 1) system: {error}"); traceback.print_exc()
|
| 565 |
-
return []
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
async def re_analyze_open_trade_async(trade_data):
|
| 569 |
-
"""(V8.7) إضافة إصلاح ndarray + إصلاح العائد المتوقع"""
|
| 570 |
-
symbol = trade_data.get('symbol')
|
| 571 |
-
try:
|
| 572 |
-
async with state_manager.trade_analysis_lock:
|
| 573 |
-
print(f"🔄 [Re-Analyze] Starting strategic analysis for {symbol}...")
|
| 574 |
-
market_context = await data_manager_global.get_market_context_async()
|
| 575 |
-
ohlcv_data_list = []
|
| 576 |
-
temp_queue = asyncio.Queue()
|
| 577 |
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
if not ohlcv_data_list: print(f"⚠️ Failed to get re-analysis data for {symbol}"); return None
|
| 594 |
-
ohlcv_data = ohlcv_data_list[0]
|
| 595 |
-
|
| 596 |
-
print(f" 🔄 [Re-Analyze] Fetching current daily volume for {symbol}...")
|
| 597 |
-
symbol_daily_volume = await data_manager_global.get_symbol_daily_volume(symbol)
|
| 598 |
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
processed_data = await ml_processor.process_and_score_symbol_enhanced(ohlcv_data, {symbol: re_analysis_whale_data} if re_analysis_whale_data else {})
|
| 612 |
-
if not processed_data: return None
|
| 613 |
-
|
| 614 |
-
if advanced_mc_results:
|
| 615 |
-
|
| 616 |
-
# 🔴 --- START OF CHANGE (V8.7 - MC Expected Return Fix) --- 🔴
|
| 617 |
-
# (إصلاح: حساب العائد المتوقع يدوياً إذا كان مفقوداً)
|
| 618 |
-
if 'expected_return_pct' not in advanced_mc_results:
|
| 619 |
-
try:
|
| 620 |
-
mean_price = advanced_mc_results.get('distribution_summary', {}).get('mean_price', 0)
|
| 621 |
-
current_price = advanced_mc_results.get('current_price', 0)
|
| 622 |
-
if mean_price > 0 and current_price > 0:
|
| 623 |
-
expected_return_pct = (mean_price - current_price) / current_price
|
| 624 |
-
advanced_mc_results['expected_return_pct'] = expected_return_pct
|
| 625 |
-
print(f" [MC Patch] {symbol}: Calculated Expected Return: {expected_return_pct:+.2%}")
|
| 626 |
-
else:
|
| 627 |
-
advanced_mc_results['expected_return_pct'] = 0.0
|
| 628 |
-
except Exception:
|
| 629 |
-
advanced_mc_results['expected_return_pct'] = 0.0
|
| 630 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 631 |
-
|
| 632 |
-
processed_data['monte_carlo_distribution'] = advanced_mc_results
|
| 633 |
-
processed_data['monte_carlo_probability'] = advanced_mc_results.get('probability_of_gain', 0)
|
| 634 |
-
|
| 635 |
-
processed_data['raw_ohlcv'] = ohlcv_data.get('raw_ohlcv') or ohlcv_data.get('ohlcv')
|
| 636 |
-
processed_data['ohlcv'] = processed_data['raw_ohlcv']
|
| 637 |
-
processed_data['sentiment_data'] = market_context
|
| 638 |
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
vader_score = VADER_ANALYZER.polarity_scores(news_text)
|
| 645 |
-
processed_data['news_score'] = vader_score.get('compound', 0.0)
|
| 646 |
-
except Exception as e:
|
| 647 |
-
print(f" ❌ [Re-Analyze News] {symbol} - Error: {e}")
|
| 648 |
-
processed_data['news_text'] = "News analysis failed."
|
| 649 |
-
processed_data['news_score'] = 0.0
|
| 650 |
else:
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
print(f" 🔄 [Re-Analyze] Sanitizing data for {symbol} before LLM log...")
|
| 657 |
-
sanitized_processed_data = _sanitize_results_for_json(processed_data)
|
| 658 |
-
|
| 659 |
-
re_analysis_decision = await llm_service_global.re_analyze_trade_async(trade_data, sanitized_processed_data)
|
| 660 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 661 |
-
|
| 662 |
-
if re_analysis_decision:
|
| 663 |
-
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') })
|
| 664 |
-
print(f"✅ [Re-Analyze] Strategic analysis complete for {symbol}. Decision: {re_analysis_decision.get('action')}")
|
| 665 |
-
return {"symbol": symbol, "decision": re_analysis_decision, "current_price": processed_data.get('current_price')}
|
| 666 |
-
else: return None
|
| 667 |
-
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
|
| 668 |
|
|
|
|
|
|
|
|
|
|
| 669 |
|
| 670 |
async def run_bot_cycle_async():
|
| 671 |
-
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
await asyncio.sleep(1.0)
|
| 680 |
-
|
| 681 |
-
print("🔄 Starting Explorer cycle (Layer 1)...");
|
| 682 |
-
await r2_service_global.save_system_logs_async({"explorer_cycle_started": True})
|
| 683 |
-
|
| 684 |
-
if not r2_service_global.acquire_lock():
|
| 685 |
-
print("❌ Failed to acquire lock - skipping cycle (another cycle likely running)"); return
|
| 686 |
-
|
| 687 |
-
open_trades = []
|
| 688 |
-
try:
|
| 689 |
-
open_trades = await trade_manager_global.get_open_trades();
|
| 690 |
-
print(f"📋 Open trades: {len(open_trades)}")
|
| 691 |
-
|
| 692 |
-
if open_trades:
|
| 693 |
-
now = datetime.now()
|
| 694 |
-
trades_to_reanalyze = [t for t in open_trades if now >= datetime.fromisoformat(t.get('expected_target_time', now.isoformat()))]
|
| 695 |
-
|
| 696 |
-
if trades_to_reanalyze:
|
| 697 |
-
print(f"🔄 (Explorer) Re-analyzing {len(trades_to_reanalyze)} trades strategically...")
|
| 698 |
-
reanalysis_results = await asyncio.gather(*[re_analyze_open_trade_async(trade) for trade in trades_to_reanalyze], return_exceptions=True)
|
| 699 |
-
|
| 700 |
-
for i, result in enumerate(reanalysis_results):
|
| 701 |
-
trade = trades_to_reanalyze[i]
|
| 702 |
-
if isinstance(result, Exception):
|
| 703 |
-
print(f" ❌ Re-analysis failed for {trade.get('symbol')}: {result}")
|
| 704 |
-
|
| 705 |
-
elif result and result['decision'].get('action') == "UPDATE_TRADE":
|
| 706 |
-
print(f" ✅ (Explorer) Updating strategy for {trade.get('symbol')}.");
|
| 707 |
-
await trade_manager_global.update_trade_strategy(trade, result['decision'])
|
| 708 |
-
|
| 709 |
-
elif result and result['decision'].get('action') == "HOLD":
|
| 710 |
-
print(f" ℹ️ (Explorer) Holding {trade.get('symbol')}. Resetting 15-min timer.")
|
| 711 |
-
await trade_manager_global.update_trade_strategy(trade, result['decision'])
|
| 712 |
-
|
| 713 |
-
elif result and result['decision'].get('action') == "CLOSE_TRADE":
|
| 714 |
-
print(f" 🛑 (Explorer) LLM Re-analysis ordered CLOSE_TRADE for {trade.get('symbol')}. Executing...")
|
| 715 |
-
await trade_manager_global.immediate_close_trade(
|
| 716 |
-
trade.get('symbol'),
|
| 717 |
-
result['current_price'],
|
| 718 |
-
f"Strategic Exit: LLM Re-analysis ({result['decision'].get('reasoning', 'N/A')[:50]}...)"
|
| 719 |
-
)
|
| 720 |
-
|
| 721 |
-
elif result:
|
| 722 |
-
print(f" ℹ️ (Explorer) Re-analysis returned unhandled action '{result['decision'].get('action')}' for {trade.get('symbol')}.")
|
| 723 |
-
|
| 724 |
-
else:
|
| 725 |
-
print(f" ⚠️ Re-analysis for {trade.get('symbol')} yielded no decision.")
|
| 726 |
-
|
| 727 |
-
current_open_trades_count = len(await trade_manager_global.get_open_trades())
|
| 728 |
-
should_look_for_new_trade = current_open_trades_count == 0
|
| 729 |
-
|
| 730 |
-
if should_look_for_new_trade:
|
| 731 |
-
portfolio_state = await r2_service_global.get_portfolio_state_async();
|
| 732 |
-
current_capital = portfolio_state.get("current_capital_usd", 0)
|
| 733 |
-
if current_capital > 1:
|
| 734 |
-
print("🎯 (Explorer) Looking for new trading opportunities...")
|
| 735 |
-
|
| 736 |
-
sentry_watchlist = await run_3_layer_analysis_explorer()
|
| 737 |
-
|
| 738 |
-
if sentry_watchlist:
|
| 739 |
-
print(f"✅ (Explorer) Found {len(sentry_watchlist)} candidates. Sending to Sentry (Layer 2)...")
|
| 740 |
-
await trade_manager_global.update_sentry_watchlist(sentry_watchlist)
|
| 741 |
-
else:
|
| 742 |
-
print("❌ (Explorer) No suitable trading opportunities found for Sentry.")
|
| 743 |
-
await trade_manager_global.update_sentry_watchlist([])
|
| 744 |
-
else:
|
| 745 |
-
print("❌ Insufficient capital to open new trades")
|
| 746 |
-
else:
|
| 747 |
-
print("ℹ️ A trade is already open, skipping new trade search.")
|
| 748 |
-
await trade_manager_global.update_sentry_watchlist([])
|
| 749 |
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 754 |
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
if r2_service_global and r2_service_global.lock_acquired: r2_service_global.release_lock()
|
| 759 |
|
| 760 |
@asynccontextmanager
|
| 761 |
async def lifespan(application: FastAPI):
|
| 762 |
-
|
| 763 |
-
"
|
| 764 |
-
|
| 765 |
-
|
| 766 |
-
|
| 767 |
-
if not success: print("❌ Application initialization failed - shutting down..."); yield; return
|
| 768 |
-
|
| 769 |
-
# (تشغيل المهام الخلفية)
|
| 770 |
asyncio.create_task(monitor_market_async())
|
| 771 |
asyncio.create_task(trade_manager_global.start_sentry_and_monitoring_loops())
|
| 772 |
-
|
| 773 |
-
|
| 774 |
-
|
| 775 |
-
|
| 776 |
-
|
| 777 |
-
print(" -> 🐋 Whale Learning Loop (V3) is scheduled")
|
| 778 |
-
else:
|
| 779 |
-
print(" -> ⚠️ Whale Learning Loop is NOT scheduled (function not found)")
|
| 780 |
-
|
| 781 |
-
await r2_service_global.save_system_logs_async({"application_started": True})
|
| 782 |
-
print("🎯 Application ready - Explorer-Sentry-Executor Architecture is active")
|
| 783 |
-
print(" -> 📈 Sentry (Layer 2) & Executor (Layer 3) are active")
|
| 784 |
-
print(" -> 🧠 Periodic Distillation (Curator) is scheduled")
|
| 785 |
yield
|
| 786 |
-
except Exception as error:
|
| 787 |
-
print(f"❌ Application startup failed: {error}");
|
| 788 |
-
traceback.print_exc()
|
| 789 |
-
if r2_service_global:
|
| 790 |
-
await r2_service_global.save_system_logs_async({ "application_startup_failed": True, "error": str(error) })
|
| 791 |
-
raise
|
| 792 |
-
finally:
|
| 793 |
-
await cleanup_on_shutdown()
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
application = FastAPI(lifespan=lifespan, title="AI Trading Bot", description="Explorer-Sentry-Executor Architecture (V5.9)", version="5.9.0")
|
| 797 |
-
|
| 798 |
-
@application.get("/")
|
| 799 |
-
# ... (لا تغيير في نقاط النهاية (Endpoints)) ...
|
| 800 |
-
async def root(): return {"message": "Welcome to the AI Trading System", "system": "Explorer-Sentry-Executor", "status": "running" if state_manager.initialization_complete else "initializing", "timestamp": datetime.now().isoformat()}
|
| 801 |
-
|
| 802 |
-
@application.get("/run-cycle")
|
| 803 |
-
async def run_cycle_api():
|
| 804 |
-
if not state_manager.initialization_complete: raise HTTPException(status_code=503, detail="Services not fully initialized")
|
| 805 |
-
asyncio.create_task(run_bot_cycle_async())
|
| 806 |
-
return {"message": "Explorer (Layer 1) cycle initiated", "system": "Explorer-Sentry-Executor"}
|
| 807 |
-
|
| 808 |
-
@application.get("/health")
|
| 809 |
-
async def health_check(): return {"status": "healthy" if state_manager.initialization_complete else "initializing", "initialization_complete": state_manager.initialization_complete, "services_initialized": state_manager.services_initialized, "initialization_error": state_manager.initialization_error, "timestamp": datetime.now().isoformat(), "system_architecture": "Explorer-Sentry-Executor (V5.9)"}
|
| 810 |
-
|
| 811 |
-
@application.get("/analyze-market")
|
| 812 |
-
async def analyze_market_api():
|
| 813 |
-
if not state_manager.initialization_complete: raise HTTPException(status_code=503, detail="Services not fully initialized")
|
| 814 |
-
result = await run_3_layer_analysis_explorer()
|
| 815 |
-
if result: return {"watchlist_generated": True, "count": len(result), "top_candidate": result[0]}
|
| 816 |
-
else: return {"watchlist_generated": False, "message": "No suitable candidates found for Sentry"}
|
| 817 |
-
|
| 818 |
-
@application.get("/portfolio")
|
| 819 |
-
async def get_portfolio_api():
|
| 820 |
-
if not state_manager.initialization_complete: raise HTTPException(status_code=503, detail="Services not fully initialized")
|
| 821 |
-
try: portfolio_state = await r2_service_global.get_portfolio_state_async(); open_trades = await trade_manager_global.get_open_trades(); return {"portfolio": portfolio_state, "open_trades": open_trades, "timestamp": datetime.now().isoformat()}
|
| 822 |
-
except Exception as e: raise HTTPException(status_code=500, detail=f"Error getting portfolio: {str(e)}")
|
| 823 |
-
|
| 824 |
-
@application.get("/system-status")
|
| 825 |
-
async def get_system_status():
|
| 826 |
-
monitoring_status = trade_manager_global.get_sentry_status() if trade_manager_global else {};
|
| 827 |
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
| 839 |
-
|
| 840 |
-
|
| 841 |
-
|
| 842 |
-
|
| 843 |
-
|
| 844 |
-
|
| 845 |
-
|
| 846 |
-
|
| 847 |
-
|
| 848 |
-
|
| 849 |
-
|
| 850 |
-
|
| 851 |
-
|
| 852 |
-
|
| 853 |
-
|
| 854 |
-
|
| 855 |
-
|
| 856 |
-
|
| 857 |
-
|
| 858 |
-
|
| 859 |
-
|
| 860 |
-
|
|
|
|
| 861 |
|
| 862 |
if __name__ == "__main__":
|
| 863 |
-
|
| 864 |
-
uvicorn.run( application, host="0.0.0.0", port=7860, log_level="info", access_log=True )
|
|
|
|
| 1 |
+
# app.py (V11.0 - Full Real Data Integration & New Engines)
|
| 2 |
import os
|
| 3 |
import traceback
|
| 4 |
import signal
|
|
|
|
| 10 |
from contextlib import asynccontextmanager
|
| 11 |
from fastapi import FastAPI, HTTPException
|
| 12 |
from datetime import datetime
|
| 13 |
+
from typing import List, Dict, Any
|
| 14 |
|
| 15 |
+
# استيراد الخدمات الأساسية
|
| 16 |
try:
|
| 17 |
from r2 import R2Service
|
| 18 |
from LLM import LLMService
|
| 19 |
+
from ml_engine.data_manager import DataManager
|
| 20 |
from ml_engine.processor import MLProcessor
|
| 21 |
from learning_hub.hub_manager import LearningHubManager
|
|
|
|
| 22 |
from trade_manager import TradeManager
|
| 23 |
+
# لم نعد بحاجة لاستيراد WhaleMonitor أو NewsFetcher هنا مباشرة،
|
| 24 |
+
# سيتم التعامل معها داخل DataManager أو Processor عند الحاجة.
|
|
|
|
| 25 |
except ImportError as e:
|
| 26 |
+
print(f"❌ [App] خطأ فادح في استيراد الوحدات: {e}")
|
|
|
|
|
|
|
| 27 |
sys.exit(1)
|
| 28 |
|
| 29 |
+
# المتغيرات العالمية للنظام
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
r2_service_global = None
|
| 31 |
data_manager_global = None
|
| 32 |
llm_service_global = None
|
| 33 |
learning_hub_global = None
|
| 34 |
trade_manager_global = None
|
| 35 |
+
ml_processor_global = None # (V11.0) إضافة المعالج كمتغير عالمي
|
|
|
|
|
|
|
| 36 |
|
| 37 |
MARKET_STATE_OK = True
|
| 38 |
|
|
|
|
| 39 |
class StateManager:
|
|
|
|
| 40 |
def __init__(self):
|
| 41 |
self.market_analysis_lock = asyncio.Lock()
|
| 42 |
self.trade_analysis_lock = asyncio.Lock()
|
| 43 |
self.initialization_complete = False
|
| 44 |
self.initialization_error = None
|
| 45 |
self.services_initialized = {
|
| 46 |
+
'r2': False, 'data': False, 'llm': False, 'hub': False, 'trade': False
|
|
|
|
|
|
|
| 47 |
}
|
| 48 |
|
| 49 |
async def wait_for_initialization(self, timeout=60):
|
|
|
|
| 58 |
self.services_initialized[service_name] = True
|
| 59 |
if all(self.services_initialized.values()):
|
| 60 |
self.initialization_complete = True
|
| 61 |
+
print("🎯 [System] جميع الخدمات مهيأة وجاهزة للعمل.")
|
| 62 |
|
| 63 |
def set_initialization_error(self, error):
|
| 64 |
self.initialization_error = error
|
|
|
|
| 67 |
state_manager = StateManager()
|
| 68 |
|
| 69 |
async def initialize_services():
|
| 70 |
+
"""تهيئة مركزية لجميع خدمات النظام بترتيب محدد"""
|
| 71 |
global r2_service_global, data_manager_global, llm_service_global
|
| 72 |
+
global learning_hub_global, trade_manager_global, ml_processor_global
|
| 73 |
+
|
| 74 |
try:
|
| 75 |
+
print("🚀 [System V11.0] بدء تهيئة الخدمات...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
+
# 1. الطبقة الأساسية (R2 & Data)
|
| 78 |
+
r2_service_global = R2Service()
|
| 79 |
+
state_manager.set_service_initialized('r2')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
|
| 81 |
+
# تحميل قاعدة بيانات العقود (اختياري، يمكن أن يكون فارغاً إذا لم يستخدم)
|
| 82 |
+
contracts_db = await r2_service_global.load_contracts_db_async() or {}
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
+
# تهيئة DataManager بدون WhaleMonitor مؤقتاً (سيتم ربطه لاحقاً إذا لزم الأمر)
|
| 85 |
+
data_manager_global = DataManager(contracts_db, whale_monitor=None, r2_service=r2_service_global)
|
| 86 |
+
await data_manager_global.initialize()
|
| 87 |
+
state_manager.set_service_initialized('data')
|
| 88 |
+
|
| 89 |
+
# 2. طبقة الذكاء (LLM & Learning Hub)
|
| 90 |
+
llm_service_global = LLMService()
|
| 91 |
+
llm_service_global.r2_service = r2_service_global
|
| 92 |
+
|
| 93 |
+
learning_hub_global = LearningHubManager(r2_service_global, llm_service_global, data_manager_global)
|
|
|
|
|
|
|
|
|
|
| 94 |
await learning_hub_global.initialize()
|
| 95 |
+
llm_service_global.learning_hub = learning_hub_global # ربط عكسي
|
| 96 |
+
state_manager.set_service_initialized('llm')
|
| 97 |
+
state_manager.set_service_initialized('hub')
|
| 98 |
+
|
| 99 |
+
# 3. طبقة التنفيذ والمعالجة (Trade Manager & Processor)
|
| 100 |
+
# تهيئة المعالج المركزي الذي يربط كل شيء
|
| 101 |
+
ml_processor_global = MLProcessor(
|
| 102 |
+
market_context=None, # سيتم تحديثه في كل دورة
|
| 103 |
+
data_manager=data_manager_global,
|
| 104 |
+
learning_hub=learning_hub_global
|
| 105 |
+
)
|
| 106 |
|
|
|
|
|
|
|
|
|
|
| 107 |
trade_manager_global = TradeManager(
|
| 108 |
+
r2_service_global, learning_hub_global, data_manager_global,
|
| 109 |
+
state_manager, callback_on_close=run_bot_cycle_async
|
|
|
|
|
|
|
|
|
|
| 110 |
)
|
|
|
|
| 111 |
await trade_manager_global.initialize_sentry_exchanges()
|
| 112 |
+
state_manager.set_service_initialized('trade')
|
|
|
|
| 113 |
|
| 114 |
+
return True
|
| 115 |
+
except Exception as e:
|
| 116 |
+
error_msg = f"فشل تهيئة الخدمات: {str(e)}"
|
| 117 |
+
print(f"❌ {error_msg}")
|
| 118 |
+
state_manager.set_initialization_error(error_msg)
|
| 119 |
+
return False
|
| 120 |
|
| 121 |
async def monitor_market_async():
|
| 122 |
+
global MARKET_STATE_OK
|
|
|
|
| 123 |
try:
|
| 124 |
+
if not await state_manager.wait_for_initialization(): return
|
| 125 |
while True:
|
| 126 |
try:
|
| 127 |
async with state_manager.market_analysis_lock:
|
| 128 |
+
market_ctx = await data_manager_global.get_market_context_async()
|
| 129 |
+
ml_processor_global.market_context = market_ctx # تحديث سياق المعالج
|
| 130 |
|
| 131 |
+
# فحص بسيط لحالة السوق (يمكن تعقيده لاحقاً)
|
| 132 |
+
if market_ctx.get('btc_sentiment') == 'BEARISH' and market_ctx.get('fear_and_greed_index', 50) < 25:
|
| 133 |
+
if MARKET_STATE_OK:
|
| 134 |
+
print("⚠️ [Market Monitor] ظروف سوق هابطة شديدة. إيقاف مؤقت.")
|
| 135 |
+
MARKET_STATE_OK = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
else:
|
| 137 |
+
if not MARKET_STATE_OK:
|
| 138 |
+
print("✅ [Market Monitor] تحسن ظروف السوق. استئناف العمل.")
|
| 139 |
+
MARKET_STATE_OK = True
|
| 140 |
+
|
| 141 |
+
await asyncio.sleep(300) # فحص كل 5 دقائق
|
| 142 |
+
except Exception as e:
|
| 143 |
+
print(f"❌ [Market Monitor] خطأ: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
await asyncio.sleep(60)
|
| 145 |
+
except Exception: pass
|
| 146 |
|
| 147 |
+
async def run_explorer_cycle():
|
| 148 |
+
"""دورة المستكشف الرئيسية (Layer 1 Complete Cycle)"""
|
| 149 |
+
if not state_manager.initialization_complete:
|
| 150 |
+
print("❌ [Explorer] الخدمات غير جاهزة.")
|
| 151 |
+
return
|
| 152 |
|
| 153 |
+
print("🔭 [Explorer] بدء دورة استكشاف جديدة...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
try:
|
| 155 |
+
if not MARKET_STATE_OK:
|
| 156 |
+
print("⏸️ [Explorer] السوق في حالة توقف. تخطي الدورة.")
|
| 157 |
+
return
|
| 158 |
+
|
| 159 |
+
# 1. الغربلة الأولية السريعة (Layer 1.1)
|
| 160 |
+
# تستخدم الآن (Ranker + XGB 1h + MC) داخلياً
|
| 161 |
+
candidates_l1 = await data_manager_global.layer1_rapid_screening()
|
| 162 |
+
if not candidates_l1:
|
| 163 |
+
print("😴 [Explorer] لم يتم العثور على مرشحين في الغربلة الأولية.")
|
| 164 |
+
# تحديث الحارس بقائمة فارغة إذا لم نجد شيئاً
|
| 165 |
+
await trade_manager_global.update_sentry_watchlist([])
|
| 166 |
+
return
|
| 167 |
+
|
| 168 |
+
# 2. التحليل العميق المتوازي (Layer 1.2 & 1.3)
|
| 169 |
+
print(f"🔬 [Explorer] تحليل عميق لـ {len(candidates_l1)} عملة...")
|
| 170 |
+
analyzed_candidates = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
|
| 172 |
+
# نستخدم Queue لتدفق البيانات بين DataManager و Processor
|
| 173 |
+
data_queue = asyncio.Queue(maxsize=5)
|
| 174 |
+
# بدء منتج البيانات في الخلفية
|
| 175 |
+
producer_task = asyncio.create_task(data_manager_global.stream_ohlcv_data(candidates_l1, data_queue))
|
| 176 |
|
| 177 |
+
while True:
|
| 178 |
+
# انتظار دفعة من البيانات
|
| 179 |
+
batch = await data_queue.get()
|
| 180 |
+
if batch is None: # إشارة نهاية التدفق
|
| 181 |
+
data_queue.task_done()
|
| 182 |
+
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
+
# معالجة الدفعة بالتوازي باستخدام Processor المحدث
|
| 185 |
+
tasks = [ml_processor_global.process_and_score_symbol_enhanced(c) for c in batch]
|
| 186 |
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
|
| 188 |
+
for res in results:
|
| 189 |
+
# قبول العملات التي تجاوزت درجة 60% فقط
|
| 190 |
+
if res and isinstance(res, dict) and res.get('enhanced_final_score', 0) >= 0.60:
|
| 191 |
+
analyzed_candidates.append(res)
|
| 192 |
|
| 193 |
+
data_queue.task_done()
|
| 194 |
+
|
| 195 |
+
# التأكد من انتهاء مهمة المنتج
|
| 196 |
+
await producer_task
|
| 197 |
+
|
| 198 |
+
# 3. الترشيح النهائي والاختيار (Layer 1.4 LLM)
|
| 199 |
+
if analyzed_candidates:
|
| 200 |
+
# ترتيب حسب الدرجة النهائية الجديدة
|
| 201 |
+
analyzed_candidates.sort(key=lambda x: x['enhanced_final_score'], reverse=True)
|
| 202 |
+
top_5 = analyzed_candidates[:5]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
|
| 204 |
+
print(f"🧠 [Explorer] إرسال أفضل {len(top_5)} عملة إلى LLM للقرار النهائي...")
|
| 205 |
+
watchlist = []
|
| 206 |
+
for cand in top_5:
|
| 207 |
+
# استشارة النموذج اللغوي لاتخاذ قرار استراتيجي
|
| 208 |
+
decision = await llm_service_global.get_trading_decision(cand)
|
| 209 |
+
if decision and decision.get('action') == 'WATCH':
|
| 210 |
+
watchlist.append({
|
| 211 |
+
'symbol': cand['symbol'],
|
| 212 |
+
'strategy_hint': decision.get('strategy_to_watch', 'GENERIC'),
|
| 213 |
+
# نحتفظ بالسياق الكامل للقرار لاستخدامه لاحقاً في الحارس
|
| 214 |
+
'llm_decision_context': {'decision': decision}
|
| 215 |
+
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
|
| 217 |
+
# تحديث قائمة مراقبة الحارس بالنتائج النهائية
|
| 218 |
+
if watchlist:
|
| 219 |
+
print(f"✅ [Explorer] تم اختيار {len(watchlist)} عملة للمراقبة.")
|
| 220 |
+
await trade_manager_global.update_sentry_watchlist(watchlist)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
else:
|
| 222 |
+
print("⚠️ [Explorer] رفض LLM جميع المرشحين.")
|
| 223 |
+
await trade_manager_global.update_sentry_watchlist([])
|
| 224 |
+
else:
|
| 225 |
+
print("📉 [Explorer] لم تنجح أي عملة في التحليل العميق (Score < 60%).")
|
| 226 |
+
await trade_manager_global.update_sentry_watchlist([])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
|
| 228 |
+
except Exception as e:
|
| 229 |
+
print(f"❌ [Explorer Error] {e}")
|
| 230 |
+
traceback.print_exc()
|
| 231 |
|
| 232 |
async def run_bot_cycle_async():
|
| 233 |
+
"""دورة البوت الرئيسية (تستدعى دورياً أو عند الطلب)"""
|
| 234 |
+
if not state_manager.initialization_complete: return
|
| 235 |
+
|
| 236 |
+
# استخدام قفل R2 لمنع تداخل الدورات
|
| 237 |
+
if not r2_service_global.acquire_lock():
|
| 238 |
+
print("⚠️ [System] دورة أخرى قيد التشغيل. تخطي.")
|
| 239 |
+
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
|
| 241 |
+
try:
|
| 242 |
+
# 1. إدارة الصفقات المفتوحة (إعادة التحليل إذا لزم الأمر)
|
| 243 |
+
open_trades = await trade_manager_global.get_open_trades()
|
| 244 |
+
if open_trades:
|
| 245 |
+
now = datetime.now()
|
| 246 |
+
# إعادة تحليل الصفقات التي تجاوزت وقت هدفها المتوقع
|
| 247 |
+
trades_to_reanalyze = [t for t in open_trades if now >= datetime.fromisoformat(t.get('expected_target_time', now.isoformat()))]
|
| 248 |
+
for trade in trades_to_reanalyze:
|
| 249 |
+
print(f"🔄 [Re-Analyze] إعادة تحليل {trade['symbol']}...")
|
| 250 |
+
# (يمكن إضافة منطق إعادة التحليل هنا باستخدام LLM و Processor)
|
| 251 |
+
# حالياً سنقوم فقط بتمديد الوقت لتجنب التكرار المستمر
|
| 252 |
+
trade['expected_target_time'] = (now + timedelta(minutes=15)).isoformat()
|
| 253 |
+
await trade_manager_global._update_trade_in_db(trade)
|
| 254 |
+
|
| 255 |
+
# 2. تشغيل دورة المستكشف للبحث عن فرص جديدة
|
| 256 |
+
# (نبحث فقط إذا كان لدينا رأس مال متاح أو عدد صفقات قليل)
|
| 257 |
+
if len(open_trades) < 3:
|
| 258 |
+
await run_explorer_cycle()
|
| 259 |
+
else:
|
| 260 |
+
print("ℹ️ [System] الحد الأقصى للصفقات مفتوح. تخطي الاستكشاف.")
|
| 261 |
|
| 262 |
+
finally:
|
| 263 |
+
if r2_service_global.lock_acquired:
|
| 264 |
+
r2_service_global.release_lock()
|
|
|
|
| 265 |
|
| 266 |
@asynccontextmanager
|
| 267 |
async def lifespan(application: FastAPI):
|
| 268 |
+
"""إدارة دورة حياة التطبيق"""
|
| 269 |
+
print("🏁 [System] بدء تشغيل التطبيق...")
|
| 270 |
+
success = await initialize_services()
|
| 271 |
+
if success:
|
| 272 |
+
# تشغيل المهام الخلفية
|
|
|
|
|
|
|
|
|
|
| 273 |
asyncio.create_task(monitor_market_async())
|
| 274 |
asyncio.create_task(trade_manager_global.start_sentry_and_monitoring_loops())
|
| 275 |
+
# تشغيل دورة استكشاف أولية بعد وقت قصير
|
| 276 |
+
asyncio.get_event_loop().call_later(10, lambda: asyncio.create_task(run_bot_cycle_async()))
|
| 277 |
+
yield
|
| 278 |
+
else:
|
| 279 |
+
print("❌ [System] فشل بدء التشغيل. الخروج...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
yield
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 281 |
|
| 282 |
+
# تنظيف عند الإغلاق
|
| 283 |
+
print("🛑 [System] إيقاف التشغيل...")
|
| 284 |
+
if trade_manager_global: await trade_manager_global.stop_sentry_loops()
|
| 285 |
+
if data_manager_global: await data_manager_global.close()
|
| 286 |
+
if learning_hub_global: await learning_hub_global.shutdown()
|
| 287 |
+
|
| 288 |
+
app = FastAPI(lifespan=lifespan, title="AI Trading Bot V11.0")
|
| 289 |
+
|
| 290 |
+
@app.get("/")
|
| 291 |
+
async def root():
|
| 292 |
+
return {
|
| 293 |
+
"status": "running" if state_manager.initialization_complete else "initializing",
|
| 294 |
+
"system": "V11.0 (Real Data + XGBoost Integrated)",
|
| 295 |
+
"timestamp": datetime.now().isoformat()
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
@app.get("/force-cycle")
|
| 299 |
+
async def force_cycle():
|
| 300 |
+
"""نقطة نهاية لتشغيل دورة استكشاف يدوياً"""
|
| 301 |
+
if not state_manager.initialization_complete:
|
| 302 |
+
raise HTTPException(status_code=503, detail="System not ready")
|
| 303 |
+
asyncio.create_task(run_bot_cycle_async())
|
| 304 |
+
return {"message": "Explorer cycle initiated manually"}
|
| 305 |
+
|
| 306 |
+
@app.get("/status")
|
| 307 |
+
async def system_status():
|
| 308 |
+
"""عرض حالة النظام والحارس"""
|
| 309 |
+
sentry_status = trade_manager_global.get_sentry_status() if trade_manager_global else {}
|
| 310 |
+
return {
|
| 311 |
+
"initialization": state_manager.initialization_complete,
|
| 312 |
+
"market_ok": MARKET_STATE_OK,
|
| 313 |
+
"sentry": sentry_status,
|
| 314 |
+
"timestamp": datetime.now().isoformat()
|
| 315 |
+
}
|
| 316 |
|
| 317 |
if __name__ == "__main__":
|
| 318 |
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
|