Riy777 commited on
Commit
e10a133
·
verified ·
1 Parent(s): 05c2aed

Update ml_engine/sniper_engine.py

Browse files
Files changed (1) hide show
  1. ml_engine/sniper_engine.py +206 -65
ml_engine/sniper_engine.py CHANGED
@@ -1,5 +1,6 @@
1
  # ============================================================
2
- # 🎯 ml_engine/sniper_engine.py (V1.2 - Sniper + Order Book Logic)
 
3
  # ============================================================
4
 
5
  import os
@@ -13,56 +14,152 @@ import asyncio
13
  import traceback
14
  from typing import List, Dict, Any
15
 
16
- # --- الإعدادات الافتراضية ---
17
- DEFAULT_SNIPER_THRESHOLD = 0.60
18
- ORDER_BOOK_DEPTH = 20 # عدد المستويات التي سنحللها في الدفتر
19
- IMBALANCE_THRESHOLD = 0.55 # يجب أن تكون كفة الشراء > 55%
 
 
 
 
 
20
 
21
  # ============================================================
22
- # 🔧 دوال الميزات (كما هي سابقاً - مختصرة هنا)
23
  # ============================================================
 
24
  def _z_score_rolling(x, w=500):
 
25
  r = x.rolling(w).mean()
26
  s = x.rolling(w).std().replace(0, np.nan)
27
- return ((x - r) / s).fillna(0)
 
28
 
29
  def _add_liquidity_proxies(df):
30
- # (نفس دالة حساب الميزات السابقة تماماً)
 
 
31
  df_proxy = df.copy()
32
- # ... (بقية الكود الخاص بالميزات يبقى كما هو لضمان عمل النموذج)
33
- # سأختصر هنا لعدم التكرار، لكن افترض أن الكود الكامل للميزات موجود
 
 
 
 
 
 
34
  df_proxy['ret'] = df_proxy['close'].pct_change().fillna(0)
35
  df_proxy['dollar_vol'] = df_proxy['close'] * df_proxy['volume']
 
 
36
  df_proxy['amihud'] = (df_proxy['ret'].abs() / df_proxy['dollar_vol'].replace(0, np.nan)).fillna(np.inf)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  return df_proxy
38
 
39
  def _add_standard_features(df):
40
- # (نفس الدالة السابقة)
41
  df_feat = df.copy()
 
42
  df_feat['return_1m'] = df_feat['close'].pct_change(1)
43
  df_feat['return_3m'] = df_feat['close'].pct_change(3)
44
  df_feat['return_5m'] = df_feat['close'].pct_change(5)
45
  df_feat['return_15m'] = df_feat['close'].pct_change(15)
 
46
  df_feat['rsi_14'] = ta.rsi(df_feat['close'], length=14)
47
- # ... بقية الميزات
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  return df_feat
49
 
 
 
 
 
50
  class SniperEngine:
 
51
  def __init__(self, models_dir: str):
 
 
 
 
 
52
  self.models_dir = models_dir
53
  self.models: List[lgb.Booster] = []
54
  self.feature_names: List[str] = []
 
55
  self.threshold = DEFAULT_SNIPER_THRESHOLD
56
  self.initialized = False
57
- self.LOOKBACK_WINDOW = 500
58
- print("🎯 [SniperEngine V1.2] Created with Order Book Logic.")
 
 
59
 
60
  async def initialize(self):
 
 
 
61
  print(f"🎯 [SniperEngine] Loading models from {self.models_dir}...")
62
  try:
 
63
  model_files = [f for f in os.listdir(self.models_dir) if f.startswith('lgbm_guard_v3_fold_')]
64
- if len(model_files) < 5:
65
- print(f"❌ [SniperEngine] Error: Models missing in {self.models_dir}")
 
66
  return
67
 
68
  for f in sorted(model_files):
@@ -71,16 +168,36 @@ class SniperEngine:
71
 
72
  self.feature_names = self.models[0].feature_name()
73
  self.initialized = True
74
- print(f"✅ [SniperEngine] Ready. Threshold: {self.threshold}")
 
75
  except Exception as e:
76
  print(f"❌ [SniperEngine] Init failed: {e}")
 
77
  self.initialized = False
78
 
79
  def set_entry_threshold(self, new_threshold: float):
80
- self.threshold = new_threshold
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
  # ==============================================================================
83
- # 📊 1. منطق تحليل دفتر الطلبات (The New Logic)
84
  # ==============================================================================
85
  def _analyze_order_book(self, order_book: Dict[str, Any]) -> Dict[str, Any]:
86
  """
@@ -91,32 +208,33 @@ class SniperEngine:
91
  asks = order_book.get('asks', []) # طلبات البيع [price, size]
92
 
93
  if not bids or not asks:
94
- return {'approved': False, 'reason': 'Empty Order Book'}
95
 
96
- # نأخذ فقط أقرب 20 مستوى (العمق المؤثر لحظياً)
97
  depth = ORDER_BOOK_DEPTH
98
  top_bids = bids[:depth]
99
  top_asks = asks[:depth]
100
 
101
- # 1. حساب إجمالي السيولة (Total Liquidity)
102
  total_bid_vol = sum([float(x[1]) for x in top_bids])
103
  total_ask_vol = sum([float(x[1]) for x in top_asks])
104
  total_vol = total_bid_vol + total_ask_vol
105
 
106
- if total_vol == 0: return {'approved': False, 'reason': 'Zero Liquidity'}
 
107
 
108
  # 2. نسبة ضغط الشراء (Bid Imbalance)
109
- # إذا كانت > 0.5 تعني المشترين أقوى
110
  bid_imbalance = total_bid_vol / total_vol
111
 
112
  # 3. فحص جدران الحيتان (Whale Walls)
113
- # هل هناك طلب بيع واحد يمثل أكثر من 30% من مجموع العرض؟ هذا جدار مخيف.
114
- max_ask_wall = max([float(x[1]) for x in top_asks])
115
- ask_wall_ratio = max_ask_wall / total_ask_vol
116
 
117
  # القرار المنطقي:
118
- # - يجب أن يكون ضغط الشراء > 0.55 (أو حسب الإعداد)
119
- # - يجب ألا يوجد جدار بيع ضخم يسد الطريق (> 0.40 مثلاً)
120
 
121
  is_bullish_book = (bid_imbalance >= IMBALANCE_THRESHOLD)
122
  no_huge_wall = (ask_wall_ratio < 0.40)
@@ -125,72 +243,95 @@ class SniperEngine:
125
  reason = f"Imbal: {bid_imbalance:.2f}, Wall: {ask_wall_ratio:.2f}"
126
 
127
  if not is_bullish_book: reason += " (Weak Bids)"
128
- if not no_huge_wall: reason += " (Ask Wall Detected)"
129
 
130
  return {
131
  'approved': (is_bullish_book and no_huge_wall),
132
- 'bid_imbalance': bid_imbalance,
133
- 'ask_wall_ratio': ask_wall_ratio,
134
  'reason': reason
135
  }
136
 
137
  except Exception as e:
138
- return {'approved': False, 'reason': f"OB Error: {e}"}
139
 
140
  # ==============================================================================
141
- # 🎯 2. دالة الفحص الرئيسية (Updated)
142
  # ==============================================================================
143
- async def check_entry_signal_async(self, ohlcv_1m_data: List[List], order_book_data: Dict[str, Any]) -> Dict[str, Any]:
144
  """
145
- التحقق من الدخول بدمج: ML Model Score + Order Book Reality
 
 
 
146
  """
147
  if not self.initialized:
148
- return {'signal': 'WAIT', 'reason': 'Not initialized'}
149
 
150
- # 1. تحليل النموذج (ML Score)
151
- # ---------------------------------------------------
 
 
152
  try:
153
  df = pd.DataFrame(ohlcv_1m_data, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
154
  df[['open', 'high', 'low', 'close', 'volume']] = df[['open', 'high', 'low', 'close', 'volume']].astype(float)
155
 
156
- # (نستخدم دوال الميزات الداخلية هنا)
157
- df_feat = _add_standard_features(df)
158
- df_feat = _add_liquidity_proxies(df_feat)
159
- df_final = df_feat.replace([np.inf, -np.inf], np.nan)
160
 
161
- if df_final.empty: return {'signal': 'WAIT', 'reason': 'Feat Calc Failed'}
 
162
 
163
- X_live = df_final.iloc[-1:][self.feature_names].fillna(0)
 
 
 
 
 
 
164
 
165
- # التنبؤ المجمع (Ensemble)
166
- preds = [m.predict(X_live)[0][1] for m in self.models] # بافتراض Binary Class 1
167
- ml_confidence = float(np.mean(preds))
168
 
 
 
169
  except Exception as e:
170
- print(f"❌ [Sniper] ML Error: {e}")
171
- return {'signal': 'WAIT', 'reason': f"ML Error: {e}"}
 
172
 
173
- # 2. تحليل دفتر الطلبات (Order Book Reality)
174
- # ---------------------------------------------------
175
- ob_analysis = self._analyze_order_book(order_book_data)
176
-
177
- # 3. القرار المدمج (The Fusion Logic)
178
- # ---------------------------------------------------
179
 
180
- # الحالة أ: النموذج قوي جداً (Super Strong) -> نتساهل قليلاً مع الدفتر
 
 
 
181
  if ml_confidence >= 0.85:
182
- final_signal = 'BUY' if ob_analysis['bid_imbalance'] >= 0.45 else 'WAIT' # فقط نضمن عدم وجود انهيار
183
- fusion_reason = f"Strong ML ({ml_confidence:.2f}) override"
 
 
 
 
 
184
 
185
- # الحالة ب: النموذج جيد (Good) -> نطلب تأكيد الدفتر القياسي
186
  elif ml_confidence >= self.threshold:
187
- final_signal = 'BUY' if ob_analysis['approved'] else 'WAIT'
188
- fusion_reason = f"ML Pass ({ml_confidence:.2f}) + OB {ob_analysis['reason']}"
189
-
190
- # الحالة ج: النموذج ضعيف
 
 
 
 
191
  else:
192
  final_signal = 'WAIT'
193
- fusion_reason = f"Low ML ({ml_confidence:.2f})"
194
 
195
  return {
196
  'signal': final_signal,
 
1
  # ============================================================
2
+ # 🎯 ml_engine/sniper_engine.py (V1.3 - GEM-Architect Edition)
3
+ # Full Logic Restored + Order Book Integration
4
  # ============================================================
5
 
6
  import os
 
14
  import traceback
15
  from typing import List, Dict, Any
16
 
17
+ # --- [ 💡 إعدادات القناص ] ---
18
+ DEFAULT_SNIPER_THRESHOLD = 0.60
19
+
20
+ # إعدادات دفتر الطلبات (Order Book Config)
21
+ ORDER_BOOK_DEPTH = 20 # عمق المستويات المحللة
22
+ IMBALANCE_THRESHOLD = 0.55 # عتبة سيطرة المشترين المطلوبة
23
+
24
+ N_SPLITS = 5
25
+ LOOKBACK_WINDOW = 500 # الحد الأدنى للشموع المطلوبة لحساب الميزات
26
 
27
  # ============================================================
28
+ # 🔧 1. دوال هندسة الميزات (Feature Engineering - FULL)
29
  # ============================================================
30
+
31
  def _z_score_rolling(x, w=500):
32
+ """حساب Z-Score المتدحرج (آمن من القسمة على صفر)"""
33
  r = x.rolling(w).mean()
34
  s = x.rolling(w).std().replace(0, np.nan)
35
+ z = (x - r) / s
36
+ return z.fillna(0)
37
 
38
  def _add_liquidity_proxies(df):
39
+ """
40
+ إضافة بدائل السيولة وتدفق الطلب المتقدمة (استعادة كاملة).
41
+ """
42
  df_proxy = df.copy()
43
+ if 'datetime' not in df_proxy.index:
44
+ if 'timestamp' in df_proxy.columns:
45
+ df_proxy['datetime'] = pd.to_datetime(df_proxy['timestamp'], unit='ms')
46
+ df_proxy = df_proxy.set_index('datetime')
47
+ else:
48
+ # في حال عدم وجود توقيت، نستخدم الفهرس الافتراضي
49
+ pass
50
+
51
  df_proxy['ret'] = df_proxy['close'].pct_change().fillna(0)
52
  df_proxy['dollar_vol'] = df_proxy['close'] * df_proxy['volume']
53
+
54
+ # Amihud Illiquidity Ratio
55
  df_proxy['amihud'] = (df_proxy['ret'].abs() / df_proxy['dollar_vol'].replace(0, np.nan)).fillna(np.inf)
56
+
57
+ # Roll Spread (Roll's Measure)
58
+ dp = df_proxy['close'].diff()
59
+ roll_cov = dp.rolling(64).cov(dp.shift(1))
60
+ df_proxy['roll_spread'] = (2 * np.sqrt(np.maximum(0, -roll_cov))).bfill()
61
+
62
+ # Order Flow Imbalance (OFI) Proxy
63
+ sign = np.sign(df_proxy['close'].diff()).fillna(0)
64
+ df_proxy['signed_vol'] = sign * df_proxy['volume']
65
+ df_proxy['ofi'] = df_proxy['signed_vol'].rolling(30).sum().fillna(0)
66
+
67
+ # VPIN (Volume-Synchronized Probability of Informed Trading) Proxy
68
+ buy_vol = (sign > 0) * df_proxy['volume']
69
+ sell_vol = (sign < 0) * df_proxy['volume']
70
+ imb = (buy_vol.rolling(60).sum() - sell_vol.rolling(60).sum()).abs()
71
+ tot = df_proxy['volume'].rolling(60).sum()
72
+ df_proxy['vpin'] = (imb / tot.replace(0, np.nan)).fillna(0)
73
+
74
+ # Garman-Klass Volatility
75
+ df_proxy['rv_gk'] = (np.log(df_proxy['high'] / df_proxy['low'])**2) / 2 - \
76
+ (2 * np.log(2) - 1) * (np.log(df_proxy['close'] / df_proxy['open'])**2)
77
+
78
+ # VWAP Deviation
79
+ vwap_window = 20
80
+ df_proxy['vwap'] = (df_proxy['close'] * df_proxy['volume']).rolling(vwap_window).sum() / \
81
+ df_proxy['volume'].rolling(vwap_window).sum()
82
+ df_proxy['vwap_dev'] = (df_proxy['close'] - df_proxy['vwap']).fillna(0)
83
+
84
+ # Composite Liquidity Score (L-Score)
85
+ df_proxy['L_score'] = (
86
+ _z_score_rolling(df_proxy['volume']) +
87
+ _z_score_rolling(1 / df_proxy['amihud'].replace(np.inf, np.nan)) +
88
+ _z_score_rolling(-df_proxy['roll_spread']) +
89
+ _z_score_rolling(-df_proxy['rv_gk'].abs()) +
90
+ _z_score_rolling(-df_proxy['vwap_dev'].abs()) +
91
+ _z_score_rolling(df_proxy['ofi'])
92
+ )
93
+
94
  return df_proxy
95
 
96
  def _add_standard_features(df):
97
+ """إضافة الميزات القياسية (عوائد، زخم، حجم)"""
98
  df_feat = df.copy()
99
+
100
  df_feat['return_1m'] = df_feat['close'].pct_change(1)
101
  df_feat['return_3m'] = df_feat['close'].pct_change(3)
102
  df_feat['return_5m'] = df_feat['close'].pct_change(5)
103
  df_feat['return_15m'] = df_feat['close'].pct_change(15)
104
+
105
  df_feat['rsi_14'] = ta.rsi(df_feat['close'], length=14)
106
+
107
+ ema_9 = ta.ema(df_feat['close'], length=9)
108
+ ema_21 = ta.ema(df_feat['close'], length=21)
109
+
110
+ # حماية من القيم الفارغة في البداية
111
+ if ema_9 is not None:
112
+ df_feat['ema_9_slope'] = (ema_9 - ema_9.shift(1)) / ema_9.shift(1)
113
+ else:
114
+ df_feat['ema_9_slope'] = 0
115
+
116
+ if ema_21 is not None:
117
+ df_feat['ema_21_dist'] = (df_feat['close'] - ema_21) / ema_21
118
+ else:
119
+ df_feat['ema_21_dist'] = 0
120
+
121
+ df_feat['atr'] = ta.atr(df_feat['high'], df_feat['low'], df_feat['close'], length=100)
122
+ df_feat['vol_zscore_50'] = _z_score_rolling(df_feat['volume'], w=50)
123
+
124
+ df_feat['candle_range'] = df_feat['high'] - df_feat['low']
125
+ df_feat['close_pos_in_range'] = (df_feat['close'] - df_feat['low']) / (df_feat['candle_range'].replace(0, np.nan))
126
+
127
  return df_feat
128
 
129
+ # ============================================================
130
+ # 🎯 2. كلاس المحرك الرئيسي (SniperEngine V1.3)
131
+ # ============================================================
132
+
133
  class SniperEngine:
134
+
135
  def __init__(self, models_dir: str):
136
+ """
137
+ تهيئة محرك قناص الدخول (L4 Sniper) مع منطق دفتر الطلبات.
138
+ Args:
139
+ models_dir: المسار المباشر للمجلد (e.g., "ml_models/guard_v2")
140
+ """
141
  self.models_dir = models_dir
142
  self.models: List[lgb.Booster] = []
143
  self.feature_names: List[str] = []
144
+
145
  self.threshold = DEFAULT_SNIPER_THRESHOLD
146
  self.initialized = False
147
+
148
+ self.LOOKBACK_WINDOW = LOOKBACK_WINDOW
149
+
150
+ print("🎯 [SniperEngine V1.3] Created (Full Math + Order Book Logic).")
151
 
152
  async def initialize(self):
153
+ """
154
+ تحميل النماذج الخمسة (Ensemble) وقائمة الميزات.
155
+ """
156
  print(f"🎯 [SniperEngine] Loading models from {self.models_dir}...")
157
  try:
158
+ # البحث عن ملفات النماذج
159
  model_files = [f for f in os.listdir(self.models_dir) if f.startswith('lgbm_guard_v3_fold_')]
160
+
161
+ if len(model_files) < N_SPLITS:
162
+ print(f"❌ [SniperEngine] Error: Found {len(model_files)} models, need {N_SPLITS}.")
163
  return
164
 
165
  for f in sorted(model_files):
 
168
 
169
  self.feature_names = self.models[0].feature_name()
170
  self.initialized = True
171
+ print(f"✅ [SniperEngine] Loaded {len(self.models)} models. Threshold: {self.threshold * 100:.1f}%")
172
+
173
  except Exception as e:
174
  print(f"❌ [SniperEngine] Init failed: {e}")
175
+ traceback.print_exc()
176
  self.initialized = False
177
 
178
  def set_entry_threshold(self, new_threshold: float):
179
+ """تحديث العتبة أثناء التشغيل"""
180
+ if 0.30 <= new_threshold <= 0.90:
181
+ print(f"🎯 [SniperEngine] Threshold updated: {self.threshold} -> {new_threshold}")
182
+ self.threshold = new_threshold
183
+ else:
184
+ print(f"⚠️ [SniperEngine] Invalid threshold: {new_threshold}")
185
+
186
+ def _calculate_features_live(self, df_1m: pd.DataFrame) -> pd.DataFrame:
187
+ """
188
+ تطبيق خط أنابيب الميزات الكامل (القياسي + المتقدم).
189
+ """
190
+ try:
191
+ df_with_std_feats = _add_standard_features(df_1m)
192
+ df_with_all_feats = _add_liquidity_proxies(df_with_std_feats)
193
+ df_final = df_with_all_feats.replace([np.inf, -np.inf], np.nan)
194
+ return df_final
195
+ except Exception as e:
196
+ print(f"❌ [SniperEngine] Feature calc error: {e}")
197
+ return pd.DataFrame()
198
 
199
  # ==============================================================================
200
+ # 📊 3. منطق تحليل دفتر الطلبات (Order Book Logic)
201
  # ==============================================================================
202
  def _analyze_order_book(self, order_book: Dict[str, Any]) -> Dict[str, Any]:
203
  """
 
208
  asks = order_book.get('asks', []) # طلبات البيع [price, size]
209
 
210
  if not bids or not asks:
211
+ return {'approved': False, 'reason': 'Empty OB', 'bid_imbalance': 0.0, 'ask_wall_ratio': 0.0}
212
 
213
+ # نأخذ فقط العمق المؤثر لحظي��ً
214
  depth = ORDER_BOOK_DEPTH
215
  top_bids = bids[:depth]
216
  top_asks = asks[:depth]
217
 
218
+ # 1. حساب إجمالي السيولة
219
  total_bid_vol = sum([float(x[1]) for x in top_bids])
220
  total_ask_vol = sum([float(x[1]) for x in top_asks])
221
  total_vol = total_bid_vol + total_ask_vol
222
 
223
+ if total_vol == 0:
224
+ return {'approved': False, 'reason': 'Zero Liquidity', 'bid_imbalance': 0.0, 'ask_wall_ratio': 0.0}
225
 
226
  # 2. نسبة ضغط الشراء (Bid Imbalance)
227
+ # النسبة المئوية للسيولة التي تمثل طلبات شراء
228
  bid_imbalance = total_bid_vol / total_vol
229
 
230
  # 3. فحص جدران الحيتان (Whale Walls)
231
+ # هل هناك طلب بيع واحد يمثل أكثر من 40% من مجموع العرض القريب؟
232
+ max_ask_wall = max([float(x[1]) for x in top_asks]) if top_asks else 0
233
+ ask_wall_ratio = max_ask_wall / total_ask_vol if total_ask_vol > 0 else 0
234
 
235
  # القرار المنطقي:
236
+ # - يجب أن يكون ضغط الشراء جيداً (>= IMBALANCE_THRESHOLD)
237
+ # - يجب ألا يوجد جدار بيع ضخم يسد الطريق (< 0.40)
238
 
239
  is_bullish_book = (bid_imbalance >= IMBALANCE_THRESHOLD)
240
  no_huge_wall = (ask_wall_ratio < 0.40)
 
243
  reason = f"Imbal: {bid_imbalance:.2f}, Wall: {ask_wall_ratio:.2f}"
244
 
245
  if not is_bullish_book: reason += " (Weak Bids)"
246
+ if not no_huge_wall: reason += " (Ask Wall)"
247
 
248
  return {
249
  'approved': (is_bullish_book and no_huge_wall),
250
+ 'bid_imbalance': float(bid_imbalance),
251
+ 'ask_wall_ratio': float(ask_wall_ratio),
252
  'reason': reason
253
  }
254
 
255
  except Exception as e:
256
+ return {'approved': False, 'reason': f"OB Error: {e}", 'bid_imbalance': 0.0, 'ask_wall_ratio': 0.0}
257
 
258
  # ==============================================================================
259
+ # 🎯 4. دالة الفحص الرئيسية (Fusion: ML + Order Book)
260
  # ==============================================================================
261
+ async def check_entry_signal_async(self, ohlcv_1m_data: List[List], order_book_data: Dict[str, Any] = None) -> Dict[str, Any]:
262
  """
263
+ الدالة الرئيسية: التحقق من إشارة الدخول (ML + OB).
264
+ Args:
265
+ ohlcv_1m_data: قائمة الشموع (لتحليل ML).
266
+ order_book_data: بيانات الدفتر (لتحليل السيولة). إذا كانت None سيتم الاعتماد على ML فقط.
267
  """
268
  if not self.initialized:
269
+ return {'signal': 'WAIT', 'reason': 'Sniper Engine not initialized'}
270
 
271
+ if len(ohlcv_1m_data) < self.LOOKBACK_WINDOW:
272
+ return {'signal': 'WAIT', 'reason': f'Insufficient 1m data ({len(ohlcv_1m_data)} < {self.LOOKBACK_WINDOW})'}
273
+
274
+ # --- أ. تحليل النموذج (ML Prediction) ---
275
  try:
276
  df = pd.DataFrame(ohlcv_1m_data, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
277
  df[['open', 'high', 'low', 'close', 'volume']] = df[['open', 'high', 'low', 'close', 'volume']].astype(float)
278
 
279
+ df_features = self._calculate_features_live(df)
 
 
 
280
 
281
+ if df_features.empty:
282
+ return {'signal': 'WAIT', 'reason': 'Feature calculation failed'}
283
 
284
+ latest_features_row = df_features.iloc[-1:]
285
+ X_live = latest_features_row[self.feature_names].fillna(0)
286
+
287
+ # تجميع التوقعات من النماذج الخمسة
288
+ all_probs = []
289
+ for model in self.models:
290
+ all_probs.append(model.predict(X_live))
291
 
292
+ stacked_probs = np.stack(all_probs)
293
+ mean_probs = np.mean(stacked_probs, axis=0)
 
294
 
295
+ ml_confidence = float(mean_probs[0][1]) # احتمالية الصنف 1 (Buy)
296
+
297
  except Exception as e:
298
+ print(f"❌ [Sniper] ML Predict Error: {e}")
299
+ traceback.print_exc()
300
+ return {'signal': 'WAIT', 'reason': f'ML Exception: {e}'}
301
 
302
+ # --- ب. تحليل دفتر الطلبات (Order Book Reality) ---
303
+ ob_analysis = {'approved': True, 'reason': 'No OB Data', 'bid_imbalance': 0.0} # افتراضي في حال عدم توفر البيانات
304
+ if order_book_data:
305
+ ob_analysis = self._analyze_order_book(order_book_data)
306
+
307
+ # --- ج. منطق الدمج (The Fusion Logic) ---
308
 
309
+ final_signal = 'WAIT'
310
+ fusion_reason = ""
311
+
312
+ # 1. حالة الثقة القصوى (Override): إذا كان النموذج واثقاً جداً (> 0.85)، نتساهل مع الدفتر
313
  if ml_confidence >= 0.85:
314
+ # فقط نتحقق من عدم وجود انهيار كارثي في الطلبات (< 0.35)
315
+ if ob_analysis.get('bid_imbalance', 1.0) >= 0.35:
316
+ final_signal = 'BUY'
317
+ fusion_reason = f"Strong ML ({ml_confidence:.2f}) override"
318
+ else:
319
+ final_signal = 'WAIT'
320
+ fusion_reason = f"Strong ML blocked by Severe Sell Pressure ({ob_analysis.get('bid_imbalance'):.2f})"
321
 
322
+ # 2. حالة الثقة العادية (Standard): نحتاج موافقة الدفتر
323
  elif ml_confidence >= self.threshold:
324
+ if ob_analysis['approved']:
325
+ final_signal = 'BUY'
326
+ fusion_reason = f"ML ({ml_confidence:.2f}) + OB Approved"
327
+ else:
328
+ final_signal = 'WAIT'
329
+ fusion_reason = f"ML Pass but OB Rejected: {ob_analysis['reason']}"
330
+
331
+ # 3. فشل النموذج
332
  else:
333
  final_signal = 'WAIT'
334
+ fusion_reason = f"Low ML Confidence ({ml_confidence:.2f})"
335
 
336
  return {
337
  'signal': final_signal,