|
|
|
|
|
import pandas as pd |
|
|
import numpy as np |
|
|
import os |
|
|
from typing import List, Dict, Any |
|
|
|
|
|
try: |
|
|
import lightgbm as lgb |
|
|
LGBM_AVAILABLE = True |
|
|
except ImportError: |
|
|
print("❌❌ [Ranker V9.1] مكتبة 'lightgbm' غير موجودة. الرانكر سيفشل.") |
|
|
print(" -> قم بتثبيتها: pip install lightgbm") |
|
|
LGBM_AVAILABLE = False |
|
|
|
|
|
class Layer1Ranker: |
|
|
def __init__(self, model_path: str = "ml_models/layer1_ranker.lgbm", r2_service=None): |
|
|
""" |
|
|
تهيئة "العقل الذكي" (نموذج V9.1) للكاشف المصغر. |
|
|
|
|
|
Args: |
|
|
model_path (str): المسار المحلي إلى ملف نموذج LightGBM المدرب. |
|
|
r2_service: (اختياري) خدمة R2 لتحميل النموذج من السحابة. |
|
|
""" |
|
|
if not LGBM_AVAILABLE: |
|
|
raise ImportError("مكتبة 'lightgbm' مطلوبة لتشغيل الرانكر.") |
|
|
|
|
|
self.model_path = model_path |
|
|
self.model_name = os.path.basename(model_path) |
|
|
self.r2_service = r2_service |
|
|
self.model = None |
|
|
self.features_in_ = None |
|
|
|
|
|
async def initialize(self): |
|
|
""" |
|
|
تحميل النموذج المدرب وميزاته. |
|
|
(ملاحظة: حالياً يحمل من ملف محلي. يمكن تعديله ليحمل من R2) |
|
|
""" |
|
|
print(f"🔄 [Ranker V9.1] بدء تحميل نموذج الكاشف المصغر: {self.model_name}...") |
|
|
try: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if self.model is None: |
|
|
if not os.path.exists(self.model_path): |
|
|
print(f"❌❌ [Ranker V9.1] خطأ فادح: ملف النموذج {self.model_path} غير موجود.") |
|
|
print(" -> هل قمت بتنزيل النموذج 'layer1_ranker.lgbm' من Colab؟") |
|
|
print(" -> سيتم استخدام 'نموذج وهمي' (Placeholder) مؤقت. هذا سيمنع أي تداول.") |
|
|
self.model = None |
|
|
|
|
|
self.features_in_ = [ |
|
|
'price_to_ema_50', 'price_to_ema_200', 'price_to_min_100', |
|
|
'price_to_max_100', 'slope_14_50', 'volume_zscore_50', |
|
|
'vwap_gap', 'rsi_14', 'rsi_mean_10', 'rsi_std_10', |
|
|
'mfi_14', 'mfi_mean_10', 'adx_14', 'atr_percent', |
|
|
'atr_normalized_return' |
|
|
] |
|
|
else: |
|
|
|
|
|
self.model = lgb.Booster(model_file=self.model_path) |
|
|
self.features_in_ = self.model.feature_name() |
|
|
print(f"✅ [Ranker V9.1] تم تحميل النموذج {self.model_name} بنجاح من ملف محلي.") |
|
|
print(f" -> عدد الميزات المطلوبة: {len(self.features_in_)}") |
|
|
|
|
|
except Exception as e: |
|
|
print(f"❌ [Ranker V9.1] فشل فادح في تحميل النموذج {self.model_name}: {e}") |
|
|
self.model = None |
|
|
|
|
|
def predict_proba(self, features_df: pd.DataFrame) -> np.ndarray: |
|
|
""" |
|
|
التنبؤ باحتمالية "الانطلاقة" (الدرجة 0.0 إلى 1.0) لدفعة من العملات. |
|
|
""" |
|
|
if self.model is None: |
|
|
|
|
|
print("⚠️ [Ranker V9.1] التنبؤ باستخدام 'الوضع الوهمي' (Placeholder). إرجاع 0.0") |
|
|
return np.zeros(len(features_df)) |
|
|
|
|
|
try: |
|
|
|
|
|
|
|
|
if not all(feature in features_df.columns for feature in self.features_in_): |
|
|
print("❌ [Ranker V9.1] خطأ: الميزات المدخلة لا تتطابق مع ميزات النموذج.") |
|
|
missing_features = set(self.features_in_) - set(features_df.columns) |
|
|
print(f" -> ميزات مفقودة: {missing_features}") |
|
|
return np.zeros(len(features_df)) |
|
|
|
|
|
aligned_df = features_df[self.features_in_] |
|
|
|
|
|
|
|
|
probabilities = self.model.predict(aligned_df) |
|
|
|
|
|
|
|
|
if probabilities.ndim == 2: |
|
|
|
|
|
return probabilities[:, 1] |
|
|
else: |
|
|
|
|
|
return probabilities |
|
|
|
|
|
except Exception as e: |
|
|
print(f"❌ [Ranker V9.1] خطأ أثناء التنبؤ: {e}") |
|
|
|
|
|
return np.zeros(len(features_df)) |
|
|
|
|
|
print("✅ ML Module: Layer 1 Ranker (V9.1) loaded.") |