|
|
|
|
|
|
|
|
|
|
|
import os |
|
|
import json |
|
|
import numpy as np |
|
|
import pandas as pd |
|
|
import xgboost as xgb |
|
|
import asyncio |
|
|
import io |
|
|
import logging |
|
|
import gc |
|
|
|
|
|
|
|
|
try: |
|
|
from .xgboost_pattern_v2 import transform_candles_for_ml |
|
|
except ImportError: |
|
|
print("❌ [PatternEngineV11.1] فشل استيراد 'xgboost_pattern_v2'. تأكد من وجود الملف.") |
|
|
transform_candles_for_ml = None |
|
|
|
|
|
|
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - [PatternEngine] - %(message)s') |
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
class ChartPatternAnalyzer: |
|
|
def __init__(self, r2_service=None, models_dir="ml_models/xgboost_pattern2"): |
|
|
""" |
|
|
تهيئة محرك الأنماط الجديد المعتمد على XGBoost. |
|
|
Args: |
|
|
r2_service: خدمة R2 (اختياري). |
|
|
models_dir: المجلد المحلي الذي يحتوي على نماذج JSON. |
|
|
""" |
|
|
self.r2_service = r2_service |
|
|
self.models_dir = models_dir |
|
|
self.models = {} |
|
|
|
|
|
|
|
|
self.timeframe_weights = { |
|
|
'15m': 0.40, |
|
|
'1h': 0.30, |
|
|
'5m': 0.20, |
|
|
'4h': 0.10, |
|
|
'1d': 0.00 |
|
|
} |
|
|
self.supported_timeframes = list(self.timeframe_weights.keys()) |
|
|
self.initialized = False |
|
|
|
|
|
async def initialize(self): |
|
|
""" |
|
|
تحميل جميع نماذج XGBoost المتوفرة. |
|
|
""" |
|
|
if self.initialized: return True |
|
|
|
|
|
logger.info(f"بدء تحميل نماذج XGBoost من: {self.models_dir}...") |
|
|
if not os.path.exists(self.models_dir): |
|
|
logger.error(f"❌ المجلد غير موجود: {self.models_dir}") |
|
|
return False |
|
|
|
|
|
loaded_count = 0 |
|
|
for tf in self.supported_timeframes: |
|
|
model_path = os.path.join(self.models_dir, f"xgb_{tf}.json") |
|
|
if os.path.exists(model_path): |
|
|
try: |
|
|
model = xgb.Booster() |
|
|
model.load_model(model_path) |
|
|
self.models[tf] = model |
|
|
loaded_count += 1 |
|
|
logger.info(f" ✅ تم تحميل نموذج {tf}") |
|
|
except Exception as e: |
|
|
logger.error(f" ❌ فشل تحميل نموذج {tf}: {e}") |
|
|
else: |
|
|
if self.timeframe_weights.get(tf, 0) > 0: |
|
|
logger.warning(f" ⚠️ نموذج {tf} غير موجود (مطلوب بوزن {self.timeframe_weights[tf]}).") |
|
|
|
|
|
if loaded_count > 0: |
|
|
self.initialized = True |
|
|
logger.info(f"✅ تم تهيئة المحرك. النماذج المحملة: {loaded_count}/{len(self.supported_timeframes)}") |
|
|
return True |
|
|
else: |
|
|
logger.error("❌ لم يتم تحميل أي نموذج.") |
|
|
return False |
|
|
|
|
|
async def detect_chart_patterns(self, ohlcv_data: dict) -> dict: |
|
|
""" |
|
|
تحليل الأنماط باستخدام النماذج المتاحة وتطبيق التصويت الموزون. |
|
|
""" |
|
|
if not self.initialized or not transform_candles_for_ml: |
|
|
return self._get_empty_result("Engine not initialized or pipeline missing") |
|
|
|
|
|
details = {} |
|
|
weighted_score_sum = 0.0 |
|
|
total_weight_used = 0.0 |
|
|
|
|
|
for tf, model in self.models.items(): |
|
|
candles = ohlcv_data.get(tf) |
|
|
if candles and len(candles) >= 200: |
|
|
try: |
|
|
df = pd.DataFrame(candles, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']) |
|
|
X_features = transform_candles_for_ml(df) |
|
|
|
|
|
if X_features is not None: |
|
|
dtest = xgb.DMatrix(X_features) |
|
|
prob_up = model.predict(dtest)[0] |
|
|
details[tf] = float(prob_up) |
|
|
|
|
|
weight = self.timeframe_weights.get(tf, 0.0) |
|
|
if weight > 0: |
|
|
weighted_score_sum += prob_up * weight |
|
|
total_weight_used += weight |
|
|
except Exception: |
|
|
details[tf] = None |
|
|
else: |
|
|
details[tf] = None |
|
|
|
|
|
final_score = 0.0 |
|
|
if total_weight_used > 0: |
|
|
final_score = weighted_score_sum / total_weight_used |
|
|
|
|
|
pattern_text = "Neutral" |
|
|
if final_score >= 0.60: pattern_text = "Bullish Signal" |
|
|
elif final_score <= 0.40: pattern_text = "Bearish Signal" |
|
|
|
|
|
return { |
|
|
'pattern_detected': pattern_text, |
|
|
'pattern_confidence': float(final_score), |
|
|
'details': details |
|
|
} |
|
|
|
|
|
def _get_empty_result(self, reason=""): |
|
|
return {'pattern_detected': 'Neutral / Error', 'pattern_confidence': 0.0, 'details': {'error': reason}} |
|
|
|
|
|
|
|
|
def clear_memory(self): |
|
|
"""تحرير النماذج من الذاكرة""" |
|
|
self.models.clear() |
|
|
self.initialized = False |
|
|
gc.collect() |
|
|
logger.info("🧹 [PatternEngine] تم تنظيف الذاكرة وتحرير النماذج.") |
|
|
|
|
|
print("✅ ML Module: Pattern Engine V11.1 (Memory Managed) loaded") |