Spaces:
Paused
Paused
File size: 10,928 Bytes
0bf918d ed354c8 8961ae5 69e65e0 3c14a86 0bf918d 69e65e0 3c14a86 69e65e0 3c14a86 8961ae5 3c14a86 69e65e0 6d09aa0 69e65e0 0bf918d 3c14a86 ed354c8 3c14a86 0bf918d 3c14a86 8961ae5 3c14a86 8961ae5 69e65e0 3c14a86 ed354c8 3c14a86 ed354c8 69e65e0 8961ae5 ed354c8 8961ae5 3c14a86 6d09aa0 3c14a86 69e65e0 3c14a86 69e65e0 ed354c8 69e65e0 3c14a86 69e65e0 3c14a86 69e65e0 3c14a86 8961ae5 3c14a86 69e65e0 8961ae5 69e65e0 3c14a86 69e65e0 8961ae5 69e65e0 3c14a86 8961ae5 69e65e0 8961ae5 ed354c8 69e65e0 6d09aa0 69e65e0 6d09aa0 8961ae5 69e65e0 ed354c8 6d09aa0 ed354c8 69e65e0 8961ae5 69e65e0 3c14a86 6d09aa0 3c14a86 6d09aa0 3c14a86 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 |
# ml_engine/patterns.py
# (V30.0 - GEM-Architect: Config-Injectable Edition)
import os
import gc
import logging
import numpy as np
import pandas as pd
import pandas_ta as ta
import xgboost as xgb
import warnings
# إخماد تحذيرات المكتبات للحفاظ على نظافة السجلات
warnings.filterwarnings("ignore", category=UserWarning)
# إعداد التسجيل
logging.basicConfig(level=logging.INFO, format='%(asctime)s - [PatternEngine] - %(message)s')
logger = logging.getLogger(__name__)
try:
from hurst import compute_Hc
HURST_AVAILABLE = True
except ImportError:
HURST_AVAILABLE = False
logger.warning("⚠️ مكتبة 'hurst' غير موجودة. سيتم استخدام القيمة الافتراضية.")
# ==============================================================================
# 🛠️ INTERNAL HELPER FUNCTIONS (Essential for Feature Engineering)
# ==============================================================================
def _zv(x):
"""حساب Z-Score الآمن (يتجنب القسمة على صفر)"""
with np.errstate(divide='ignore', invalid='ignore'):
x = np.asarray(x, dtype="float32")
m = np.nanmean(x)
s = np.nanstd(x) + 1e-9
x_norm = (x - m) / s
return np.nan_to_num(x_norm, nan=0.0).astype("float32")
def _ema_np_safe(x, n):
"""حساب المتوسط المتحرك الأسي (EMA) بشكل سريع باستخدام Numpy"""
x = np.asarray(x, dtype="float32")
k = 2.0 / (n + 1.0)
out = np.empty_like(x)
out[0] = x[0] if not np.isnan(x[0]) else 0.0
for i in range(1, len(x)):
val = x[i] if not np.isnan(x[i]) else out[i-1]
out[i] = out[i-1] + k * (val - out[i-1])
return out
def _mc_simple_fast(closes_np: np.ndarray, target_profit=0.005):
"""نسخة سريعة من محاكاة مونت كارلو للميزات الإحصائية"""
try:
if len(closes_np) < 30: return 0.5, 0.0
c = closes_np
cur = float(c[-1])
if cur <= 0: return 0.5, 0.0
lr = np.diff(np.log1p(c))
lr = lr[np.isfinite(lr)]
if len(lr) < 20: return 0.5, 0.0
mu = np.mean(lr)
sigma = np.std(lr)
if sigma < 1e-9: return 0.5, 0.0
n_sims = 500
drift = (mu - 0.5 * sigma**2)
diffusion = sigma * np.random.standard_t(df=10, size=n_sims)
sim_prices = cur * np.exp(drift + diffusion)
var95 = np.percentile(sim_prices, 5)
var95_pct = (cur - var95) / (cur + 1e-9)
prob_gain = np.mean(sim_prices >= cur * (1 + target_profit))
return float(prob_gain), float(var95_pct)
except Exception:
return 0.5, 0.0
def _transform_candles_for_ml(df_window: pd.DataFrame):
"""
تحويل نافذة من الشموع (200 شمعة) إلى متجه ميزات جاهز لنموذج ML.
"""
try:
if len(df_window) < 200:
return None
df = df_window.iloc[-200:].copy()
o = df["open"].to_numpy(dtype="float32")
h = df["high"].to_numpy(dtype="float32")
l = df["low"].to_numpy(dtype="float32")
c = df["close"].to_numpy(dtype="float32")
v = df["volume"].to_numpy(dtype="float32")
# 1. Basic Features
base = np.stack([o, h, l, c, v], axis=1)
base_z = _zv(base)
# 2. Extra Features
lr = np.zeros_like(c); lr[1:] = np.diff(np.log1p(c))
rng = (h - l) / (c + 1e-9)
extra = np.stack([lr, rng], axis=1)
extra_z = _zv(extra)
# 3. Technical Indicators
ema9 = _ema_np_safe(c, 9)
ema21 = _ema_np_safe(c, 21)
ema50 = _ema_np_safe(c, 50)
ema200 = _ema_np_safe(c, 200)
slope21 = np.concatenate([[0.0], np.diff(ema21)])
slope50 = np.concatenate([[0.0], np.diff(ema50)])
try: rsi = ta.rsi(pd.Series(c), length=14).fillna(50).to_numpy(dtype="float32")
except: rsi = np.full_like(c, 50.0, dtype="float32")
try:
macd_data = ta.macd(pd.Series(c), fast=12, slow=26, signal=9)
macd_line = macd_data.iloc[:, 0].fillna(0).to_numpy(dtype="float32")
macd_hist = macd_data.iloc[:, 2].fillna(0).to_numpy(dtype="float32")
except:
macd_line = np.zeros_like(c); macd_hist = np.zeros_like(c)
try: atr = ta.atr(pd.Series(h), pd.Series(l), pd.Series(c), length=14).fillna(0).to_numpy(dtype="float32")
except: atr = np.zeros_like(c)
try:
bb = ta.bbands(pd.Series(c), length=20, std=2)
bb_p = ((c - bb.iloc[:, 0]) / (bb.iloc[:, 2] - bb.iloc[:, 0] + 1e-9)).fillna(0.5).to_numpy(dtype="float32")
except: bb_p = np.full_like(c, 0.5)
try: obv = ta.obv(pd.Series(c), pd.Series(v)).fillna(0).to_numpy(dtype="float32")
except: obv = np.zeros_like(c)
indicators = np.stack([ema9, ema21, ema50, ema200, slope21, slope50, rsi, macd_line, macd_hist, atr / (c + 1e-9), bb_p, obv], axis=1)
indicators_z = _zv(indicators)
# 4. Flatten
X_seq = np.concatenate([base_z, extra_z, indicators_z], axis=1)
X_seq_flat = X_seq.reshape(1, -1)
# 5. Static Features
try: mc_p, mc_var = _mc_simple_fast(c[-100:])
except: mc_p, mc_var = 0.5, 0.0
hurst_val = 0.5
if HURST_AVAILABLE:
try: hurst_val = compute_Hc(c[-100:], kind='price', simplified=True)[0]
except: pass
X_stat = np.array([[mc_p, mc_var, hurst_val]], dtype="float32")
# 6. Final Merge
X_final = np.concatenate([X_seq_flat, X_stat], axis=1)
X_final = np.nan_to_num(X_final, nan=0.0, posinf=0.0, neginf=0.0)
return X_final
except Exception:
return None
# ==============================================================================
# 🤖 CHART PATTERN ANALYZER CLASS
# ==============================================================================
class ChartPatternAnalyzer:
def __init__(self, models_dir="ml_models/xgboost_pattern2"):
"""
تهيئة محرك الأنماط الموحد (Unified Pattern Engine).
"""
self.models_dir = models_dir
self.models = {}
# ✅ القيم الافتراضية (Placeholder)
# سيتم الكتابة عليها بواسطة Processor عند التشغيل
self.timeframe_weights = {'15m': 0.40, '1h': 0.30, '5m': 0.20, '4h': 0.10, '1d': 0.00}
self.thresh_bullish = 0.60
self.thresh_bearish = 0.40
self.supported_timeframes = list(self.timeframe_weights.keys())
self.initialized = False
def configure_thresholds(self, weights: dict, bull_thresh: float, bear_thresh: float):
"""
دالة استقبال الإعدادات من المعالج المركزي (Processor Injection).
"""
self.timeframe_weights = weights
self.thresh_bullish = bull_thresh
self.thresh_bearish = bear_thresh
self.supported_timeframes = list(weights.keys())
logger.info(f"🔧 [PatternEngine] Config Injected: Bull > {self.thresh_bullish}, Weights set.")
async def initialize(self):
"""تحميل نماذج XGBoost"""
if self.initialized: return True
logger.info(f"⚡ [PatternEngine] Loading models from {self.models_dir}...")
if not os.path.exists(self.models_dir):
logger.error(f"❌ Models directory not found: {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
except Exception as e:
logger.error(f" ❌ Failed to load {tf}: {e}")
if loaded_count > 0:
self.initialized = True
logger.info(f"✅ [PatternEngine] Initialized with {loaded_count} models.")
return True
return False
async def detect_chart_patterns(self, ohlcv_data: dict) -> dict:
"""تحليل الأنماط لكافة الأطر الزمنية المتوفرة"""
if not self.initialized:
return self._get_empty_result("Not initialized")
details = {}
weighted_score_sum = 0.0
total_weight_used = 0.0
for tf, model in self.models.items():
candles = ohlcv_data.get(tf)
# نحتاج 200 شمعة على الأقل للتحويل
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 >= self.thresh_bullish:
pattern_text = "Bullish Signal"
elif final_score <= self.thresh_bearish:
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', 'pattern_confidence': 0.0, 'details': {'error': reason}}
def clear_memory(self):
"""تنظيف الذاكرة"""
self.models.clear()
self.initialized = False
gc.collect()
logger.info("🧹 [PatternEngine] Memory cleared.") |