Riy777 commited on
Commit
3c14a86
·
verified ·
1 Parent(s): 2e06244

Update ml_engine/patterns.py

Browse files
Files changed (1) hide show
  1. ml_engine/patterns.py +173 -56
ml_engine/patterns.py CHANGED
@@ -1,59 +1,185 @@
1
  # ml_engine/patterns.py
2
- # (V11.1 - XGBoost Multi-Timeframe Engine with Memory Management)
3
 
4
  import os
5
- import json
 
6
  import numpy as np
7
  import pandas as pd
 
8
  import xgboost as xgb
9
- import asyncio
10
- import io
11
- import logging
12
- import gc # استيراد garbage collector لتنظيف الذاكرة
13
 
14
- # استيراد الـ Pipeline الجديد
15
- try:
16
- from .xgboost_pattern_v2 import transform_candles_for_ml
17
- except ImportError:
18
- print("❌ [PatternEngineV11.1] فشل استيراد 'xgboost_pattern_v2'. تأكد من وجود الملف.")
19
- transform_candles_for_ml = None
20
 
21
  # إعداد التسجيل
22
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - [PatternEngine] - %(message)s')
23
  logger = logging.getLogger(__name__)
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  class ChartPatternAnalyzer:
26
- def __init__(self, r2_service=None, models_dir="ml_models/xgboost_pattern2"):
27
  """
28
- تهيئة محرك الأنماط الجديد المعتمد على XGBoost.
29
- Args:
30
- r2_service: خدمة R2 (اختياري).
31
- models_dir: المجلد المحلي الذي يحتوي على نماذج JSON.
32
  """
33
- self.r2_service = r2_service
34
  self.models_dir = models_dir
35
- self.models = {} # لتخزين النماذج المحملة
36
-
37
- # الأطر الزمنية المدعومة وأوزان التصويت الجديدة (التركيز على القصير)
38
- self.timeframe_weights = {
39
- '15m': 0.40,
40
- '1h': 0.30,
41
- '5m': 0.20,
42
- '4h': 0.10,
43
- '1d': 0.00
44
- }
45
  self.supported_timeframes = list(self.timeframe_weights.keys())
46
  self.initialized = False
47
 
48
  async def initialize(self):
49
- """
50
- تحميل جميع نماذج XGBoost المتوفرة.
51
- """
52
  if self.initialized: return True
53
-
54
- logger.info(f"بدء تحميل نماذج XGBoost من: {self.models_dir}...")
55
  if not os.path.exists(self.models_dir):
56
- logger.error(f"❌ المجلد غير موجود: {self.models_dir}")
57
  return False
58
 
59
  loaded_count = 0
@@ -65,27 +191,19 @@ class ChartPatternAnalyzer:
65
  model.load_model(model_path)
66
  self.models[tf] = model
67
  loaded_count += 1
68
- logger.info(f" ✅ تم تحميل نموذج {tf}")
69
  except Exception as e:
70
- logger.error(f" ❌ فشل تحميل نموذج {tf}: {e}")
71
- else:
72
- if self.timeframe_weights.get(tf, 0) > 0:
73
- logger.warning(f" ⚠️ نموذج {tf} غير موجود (مطلوب بوزن {self.timeframe_weights[tf]}).")
74
-
75
  if loaded_count > 0:
76
  self.initialized = True
77
- logger.info(f"✅ تم تهيئة المحرك. النماذج المحملة: {loaded_count}/{len(self.supported_timeframes)}")
78
  return True
79
- else:
80
- logger.error("❌ لم يتم تحميل أي نموذج.")
81
- return False
82
 
83
  async def detect_chart_patterns(self, ohlcv_data: dict) -> dict:
84
- """
85
- تحليل الأنماط باستخدام النماذج المتاحة وتطبيق التصويت الموزون.
86
- """
87
- if not self.initialized or not transform_candles_for_ml:
88
- return self._get_empty_result("Engine not initialized or pipeline missing")
89
 
90
  details = {}
91
  weighted_score_sum = 0.0
@@ -93,10 +211,12 @@ class ChartPatternAnalyzer:
93
 
94
  for tf, model in self.models.items():
95
  candles = ohlcv_data.get(tf)
 
96
  if candles and len(candles) >= 200:
97
  try:
98
  df = pd.DataFrame(candles, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
99
- X_features = transform_candles_for_ml(df)
 
100
 
101
  if X_features is not None:
102
  dtest = xgb.DMatrix(X_features)
@@ -127,14 +247,11 @@ class ChartPatternAnalyzer:
127
  }
128
 
129
  def _get_empty_result(self, reason=""):
130
- return {'pattern_detected': 'Neutral / Error', 'pattern_confidence': 0.0, 'details': {'error': reason}}
131
 
132
- # 🔴 دالة جديدة لتنظيف الذاكرة
133
  def clear_memory(self):
134
- """تحرير النماذج من الذاكرة"""
135
  self.models.clear()
136
  self.initialized = False
137
  gc.collect()
138
- logger.info("🧹 [PatternEngine] تم تنظيف الذاكرة وتحرير النماذج.")
139
-
140
- print("✅ ML Module: Pattern Engine V11.1 (Memory Managed) loaded")
 
1
  # ml_engine/patterns.py
2
+ # (V12.0 - Unified Pattern Engine - XGBoost & Pipeline Merged)
3
 
4
  import os
5
+ import gc
6
+ import logging
7
  import numpy as np
8
  import pandas as pd
9
+ import pandas_ta as ta
10
  import xgboost as xgb
11
+ import warnings
 
 
 
12
 
13
+ # إخماد تحذيرات المكتبات للحفاظ على نظافة السجلات
14
+ warnings.filterwarnings("ignore", category=UserWarning)
 
 
 
 
15
 
16
  # إعداد التسجيل
17
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - [PatternEngine] - %(message)s')
18
  logger = logging.getLogger(__name__)
19
 
20
+ try:
21
+ from hurst import compute_Hc
22
+ HURST_AVAILABLE = True
23
+ except ImportError:
24
+ HURST_AVAILABLE = False
25
+ logger.warning("⚠️ مكتبة 'hurst' غير موجودة. سيتم استخدام القيمة الافتراضية.")
26
+
27
+ # ==============================================================================
28
+ # 🛠️ INTERNAL HELPER FUNCTIONS (Previously in xgboost_pattern_v2)
29
+ # ==============================================================================
30
+
31
+ def _zv(x):
32
+ """حساب Z-Score الآمن (يتجنب القسمة على صفر)"""
33
+ with np.errstate(divide='ignore', invalid='ignore'):
34
+ x = np.asarray(x, dtype="float32")
35
+ m = np.nanmean(x)
36
+ s = np.nanstd(x) + 1e-9
37
+ x_norm = (x - m) / s
38
+ return np.nan_to_num(x_norm, nan=0.0).astype("float32")
39
+
40
+ def _ema_np_safe(x, n):
41
+ """حساب المتوسط المتحرك الأسي (EMA) بشكل سريع باستخدام Numpy"""
42
+ x = np.asarray(x, dtype="float32")
43
+ k = 2.0 / (n + 1.0)
44
+ out = np.empty_like(x)
45
+ out[0] = x[0] if not np.isnan(x[0]) else 0.0
46
+ for i in range(1, len(x)):
47
+ val = x[i] if not np.isnan(x[i]) else out[i-1]
48
+ out[i] = out[i-1] + k * (val - out[i-1])
49
+ return out
50
+
51
+ def _mc_simple_fast(closes_np: np.ndarray, target_profit=0.005):
52
+ """نسخة سريعة من محاكاة مونت كارلو للميزات الإحصائية"""
53
+ try:
54
+ if len(closes_np) < 30: return 0.5, 0.0
55
+ c = closes_np
56
+ cur = float(c[-1])
57
+ if cur <= 0: return 0.5, 0.0
58
+
59
+ lr = np.diff(np.log1p(c))
60
+ lr = lr[np.isfinite(lr)]
61
+ if len(lr) < 20: return 0.5, 0.0
62
+
63
+ mu = np.mean(lr)
64
+ sigma = np.std(lr)
65
+ if sigma < 1e-9: return 0.5, 0.0
66
+
67
+ n_sims = 500
68
+ drift = (mu - 0.5 * sigma**2)
69
+ diffusion = sigma * np.random.standard_t(df=10, size=n_sims)
70
+ sim_prices = cur * np.exp(drift + diffusion)
71
+
72
+ var95 = np.percentile(sim_prices, 5)
73
+ var95_pct = (cur - var95) / (cur + 1e-9)
74
+ prob_gain = np.mean(sim_prices >= cur * (1 + target_profit))
75
+
76
+ return float(prob_gain), float(var95_pct)
77
+ except Exception:
78
+ return 0.5, 0.0
79
+
80
+ def _transform_candles_for_ml(df_window: pd.DataFrame):
81
+ """
82
+ تحويل نافذة من الشموع (200 شمعة) إلى متجه ميزات جاهز لنموذج ML.
83
+ """
84
+ try:
85
+ if len(df_window) < 200:
86
+ return None
87
+
88
+ df = df_window.iloc[-200:].copy()
89
+
90
+ o = df["open"].to_numpy(dtype="float32")
91
+ h = df["high"].to_numpy(dtype="float32")
92
+ l = df["low"].to_numpy(dtype="float32")
93
+ c = df["close"].to_numpy(dtype="float32")
94
+ v = df["volume"].to_numpy(dtype="float32")
95
+
96
+ # 1. Basic Features
97
+ base = np.stack([o, h, l, c, v], axis=1)
98
+ base_z = _zv(base)
99
+
100
+ # 2. Extra Features
101
+ lr = np.zeros_like(c); lr[1:] = np.diff(np.log1p(c))
102
+ rng = (h - l) / (c + 1e-9)
103
+ extra = np.stack([lr, rng], axis=1)
104
+ extra_z = _zv(extra)
105
+
106
+ # 3. Technical Indicators
107
+ ema9 = _ema_np_safe(c, 9)
108
+ ema21 = _ema_np_safe(c, 21)
109
+ ema50 = _ema_np_safe(c, 50)
110
+ ema200 = _ema_np_safe(c, 200)
111
+ slope21 = np.concatenate([[0.0], np.diff(ema21)])
112
+ slope50 = np.concatenate([[0.0], np.diff(ema50)])
113
+
114
+ try: rsi = ta.rsi(pd.Series(c), length=14).fillna(50).to_numpy(dtype="float32")
115
+ except: rsi = np.full_like(c, 50.0, dtype="float32")
116
+
117
+ try:
118
+ macd_data = ta.macd(pd.Series(c), fast=12, slow=26, signal=9)
119
+ macd_line = macd_data.iloc[:, 0].fillna(0).to_numpy(dtype="float32")
120
+ macd_hist = macd_data.iloc[:, 2].fillna(0).to_numpy(dtype="float32")
121
+ except:
122
+ macd_line = np.zeros_like(c); macd_hist = np.zeros_like(c)
123
+
124
+ try: atr = ta.atr(pd.Series(h), pd.Series(l), pd.Series(c), length=14).fillna(0).to_numpy(dtype="float32")
125
+ except: atr = np.zeros_like(c)
126
+
127
+ try:
128
+ bb = ta.bbands(pd.Series(c), length=20, std=2)
129
+ bb_p = ((c - bb.iloc[:, 0]) / (bb.iloc[:, 2] - bb.iloc[:, 0] + 1e-9)).fillna(0.5).to_numpy(dtype="float32")
130
+ except: bb_p = np.full_like(c, 0.5)
131
+
132
+ try: obv = ta.obv(pd.Series(c), pd.Series(v)).fillna(0).to_numpy(dtype="float32")
133
+ except: obv = np.zeros_like(c)
134
+
135
+ indicators = np.stack([ema9, ema21, ema50, ema200, slope21, slope50, rsi, macd_line, macd_hist, atr / (c + 1e-9), bb_p, obv], axis=1)
136
+ indicators_z = _zv(indicators)
137
+
138
+ # 4. Flatten
139
+ X_seq = np.concatenate([base_z, extra_z, indicators_z], axis=1)
140
+ X_seq_flat = X_seq.reshape(1, -1)
141
+
142
+ # 5. Static Features
143
+ try: mc_p, mc_var = _mc_simple_fast(c[-100:])
144
+ except: mc_p, mc_var = 0.5, 0.0
145
+
146
+ hurst_val = 0.5
147
+ if HURST_AVAILABLE:
148
+ try: hurst_val = compute_Hc(c[-100:], kind='price', simplified=True)[0]
149
+ except: pass
150
+
151
+ X_stat = np.array([[mc_p, mc_var, hurst_val]], dtype="float32")
152
+
153
+ # 6. Final Merge
154
+ X_final = np.concatenate([X_seq_flat, X_stat], axis=1)
155
+ X_final = np.nan_to_num(X_final, nan=0.0, posinf=0.0, neginf=0.0)
156
+
157
+ return X_final
158
+ except Exception:
159
+ return None
160
+
161
+ # ==============================================================================
162
+ # 🤖 CHART PATTERN ANALYZER CLASS
163
+ # ==============================================================================
164
+
165
  class ChartPatternAnalyzer:
166
+ def __init__(self, models_dir="ml_models/xgboost_pattern2"):
167
  """
168
+ تهيئة محرك الأنماط الموحد (Unified Pattern Engine).
 
 
 
169
  """
 
170
  self.models_dir = models_dir
171
+ self.models = {}
172
+ self.timeframe_weights = {'15m': 0.40, '1h': 0.30, '5m': 0.20, '4h': 0.10, '1d': 0.00}
 
 
 
 
 
 
 
 
173
  self.supported_timeframes = list(self.timeframe_weights.keys())
174
  self.initialized = False
175
 
176
  async def initialize(self):
177
+ """تحميل نماذج XGBoost"""
 
 
178
  if self.initialized: return True
179
+
180
+ logger.info(f" [PatternEngine] Loading models from {self.models_dir}...")
181
  if not os.path.exists(self.models_dir):
182
+ logger.error(f"❌ Models directory not found: {self.models_dir}")
183
  return False
184
 
185
  loaded_count = 0
 
191
  model.load_model(model_path)
192
  self.models[tf] = model
193
  loaded_count += 1
 
194
  except Exception as e:
195
+ logger.error(f" ❌ Failed to load {tf}: {e}")
196
+
 
 
 
197
  if loaded_count > 0:
198
  self.initialized = True
199
+ logger.info(f"✅ [PatternEngine] Initialized with {loaded_count} models.")
200
  return True
201
+ return False
 
 
202
 
203
  async def detect_chart_patterns(self, ohlcv_data: dict) -> dict:
204
+ """تحليل الأنماط لكافة الأطر الزمنية المتوفرة"""
205
+ if not self.initialized:
206
+ return self._get_empty_result("Not initialized")
 
 
207
 
208
  details = {}
209
  weighted_score_sum = 0.0
 
211
 
212
  for tf, model in self.models.items():
213
  candles = ohlcv_data.get(tf)
214
+ # نحتاج 200 شمعة على الأقل للتحويل
215
  if candles and len(candles) >= 200:
216
  try:
217
  df = pd.DataFrame(candles, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
218
+ # استخدام الدالة الداخلية المدمجة
219
+ X_features = _transform_candles_for_ml(df)
220
 
221
  if X_features is not None:
222
  dtest = xgb.DMatrix(X_features)
 
247
  }
248
 
249
  def _get_empty_result(self, reason=""):
250
+ return {'pattern_detected': 'Neutral', 'pattern_confidence': 0.0, 'details': {'error': reason}}
251
 
 
252
  def clear_memory(self):
253
+ """تنظيف الذاكرة"""
254
  self.models.clear()
255
  self.initialized = False
256
  gc.collect()
257
+ logger.info("🧹 [PatternEngine] Memory cleared.")