Riy777 commited on
Commit
744703f
·
verified ·
1 Parent(s): ef29313

Update ml_engine/sniper_engine.py

Browse files
Files changed (1) hide show
  1. ml_engine/sniper_engine.py +95 -129
ml_engine/sniper_engine.py CHANGED
@@ -1,6 +1,6 @@
1
  # ============================================================
2
- # 🎯 ml_engine/sniper_engine.py (V1.3 - GEM-Architect Edition)
3
- # Full Logic Restored + Order Book Integration
4
  # ============================================================
5
 
6
  import os
@@ -15,38 +15,36 @@ 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']
@@ -54,17 +52,17 @@ def _add_liquidity_proxies(df):
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()
@@ -81,7 +79,7 @@ def _add_liquidity_proxies(df):
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)) +
@@ -94,7 +92,7 @@ def _add_liquidity_proxies(df):
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)
@@ -107,7 +105,6 @@ def _add_standard_features(df):
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:
@@ -127,35 +124,26 @@ def _add_standard_features(df):
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:
@@ -168,7 +156,7 @@ class SniperEngine:
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}")
@@ -176,17 +164,9 @@ class SniperEngine:
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)
@@ -197,146 +177,132 @@ class SniperEngine:
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
  """
204
- تحليل دفتر الطلبات للبحث عن ضغط الشراء وغياب جدران البيع.
205
  """
206
  try:
207
- bids = order_book.get('bids', []) # طلبات الشراء [price, size]
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)
241
-
242
- status = "APPROVED" if (is_bullish_book and no_huge_wall) else "REJECTED"
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,
338
- 'confidence_prob': ml_confidence,
 
 
 
339
  'threshold': self.threshold,
340
- 'ob_data': ob_analysis,
341
- 'reason': fusion_reason
342
  }
 
1
  # ============================================================
2
+ # 🎯 ml_engine/sniper_engine.py (V1.5 - GEM-Architect Edition)
3
+ # Logic: Weighted Average (60% ML / 40% OB) + Safety Veto
4
  # ============================================================
5
 
6
  import os
 
15
  from typing import List, Dict, Any
16
 
17
  # --- [ 💡 إعدادات القناص ] ---
18
+ # الآن العتبة النهائية هي نتيجة المعادلة (0.6*ML + 0.4*OB)
19
+ # نرفع العتبة قليلاً لضمان الجودة (مثلاً 0.65 بدلاً من 0.60)
20
+ DEFAULT_FINAL_THRESHOLD = 0.60
21
 
22
  # إعدادات دفتر الطلبات (Order Book Config)
23
+ ORDER_BOOK_DEPTH = 20
24
+ # صمام الأمان: إذا كان جدار البيع أكبر من 40% من العرض، ارفض فوراً
25
+ CRITICAL_WALL_RATIO = 0.40
26
 
27
  N_SPLITS = 5
28
+ LOOKBACK_WINDOW = 500
29
 
30
  # ============================================================
31
+ # 🔧 1. دوال هندسة الميزات (Feature Engineering - Full)
32
  # ============================================================
33
 
34
  def _z_score_rolling(x, w=500):
35
+ """حساب Z-Score المتدحرج"""
36
  r = x.rolling(w).mean()
37
  s = x.rolling(w).std().replace(0, np.nan)
38
  z = (x - r) / s
39
  return z.fillna(0)
40
 
41
  def _add_liquidity_proxies(df):
42
+ """إضافة بدائل السيولة وتدفق الطلب المتقدمة"""
 
 
43
  df_proxy = df.copy()
44
  if 'datetime' not in df_proxy.index:
45
  if 'timestamp' in df_proxy.columns:
46
  df_proxy['datetime'] = pd.to_datetime(df_proxy['timestamp'], unit='ms')
47
  df_proxy = df_proxy.set_index('datetime')
 
 
 
48
 
49
  df_proxy['ret'] = df_proxy['close'].pct_change().fillna(0)
50
  df_proxy['dollar_vol'] = df_proxy['close'] * df_proxy['volume']
 
52
  # Amihud Illiquidity Ratio
53
  df_proxy['amihud'] = (df_proxy['ret'].abs() / df_proxy['dollar_vol'].replace(0, np.nan)).fillna(np.inf)
54
 
55
+ # Roll Spread
56
  dp = df_proxy['close'].diff()
57
  roll_cov = dp.rolling(64).cov(dp.shift(1))
58
  df_proxy['roll_spread'] = (2 * np.sqrt(np.maximum(0, -roll_cov))).bfill()
59
 
60
+ # Order Flow Imbalance (OFI)
61
  sign = np.sign(df_proxy['close'].diff()).fillna(0)
62
  df_proxy['signed_vol'] = sign * df_proxy['volume']
63
  df_proxy['ofi'] = df_proxy['signed_vol'].rolling(30).sum().fillna(0)
64
 
65
+ # VPIN Proxy
66
  buy_vol = (sign > 0) * df_proxy['volume']
67
  sell_vol = (sign < 0) * df_proxy['volume']
68
  imb = (buy_vol.rolling(60).sum() - sell_vol.rolling(60).sum()).abs()
 
79
  df_proxy['volume'].rolling(vwap_window).sum()
80
  df_proxy['vwap_dev'] = (df_proxy['close'] - df_proxy['vwap']).fillna(0)
81
 
82
+ # Composite Liquidity Score
83
  df_proxy['L_score'] = (
84
  _z_score_rolling(df_proxy['volume']) +
85
  _z_score_rolling(1 / df_proxy['amihud'].replace(np.inf, np.nan)) +
 
92
  return df_proxy
93
 
94
  def _add_standard_features(df):
95
+ """إضافة الميزات القياسية"""
96
  df_feat = df.copy()
97
 
98
  df_feat['return_1m'] = df_feat['close'].pct_change(1)
 
105
  ema_9 = ta.ema(df_feat['close'], length=9)
106
  ema_21 = ta.ema(df_feat['close'], length=21)
107
 
 
108
  if ema_9 is not None:
109
  df_feat['ema_9_slope'] = (ema_9 - ema_9.shift(1)) / ema_9.shift(1)
110
  else:
 
124
  return df_feat
125
 
126
  # ============================================================
127
+ # 🎯 2. كلاس المحرك الرئيسي (SniperEngine V1.5)
128
  # ============================================================
129
 
130
  class SniperEngine:
131
 
132
  def __init__(self, models_dir: str):
 
 
 
 
 
133
  self.models_dir = models_dir
134
  self.models: List[lgb.Booster] = []
135
  self.feature_names: List[str] = []
136
 
137
+ self.threshold = DEFAULT_FINAL_THRESHOLD
138
  self.initialized = False
 
139
  self.LOOKBACK_WINDOW = LOOKBACK_WINDOW
140
 
141
+ print("🎯 [SniperEngine V1.5] Created (Weighted Logic 60/40 + Safety Veto).")
142
 
143
  async def initialize(self):
144
+ """تحميل النماذج"""
 
 
145
  print(f"🎯 [SniperEngine] Loading models from {self.models_dir}...")
146
  try:
 
147
  model_files = [f for f in os.listdir(self.models_dir) if f.startswith('lgbm_guard_v3_fold_')]
148
 
149
  if len(model_files) < N_SPLITS:
 
156
 
157
  self.feature_names = self.models[0].feature_name()
158
  self.initialized = True
159
+ print(f"✅ [SniperEngine] Loaded {len(self.models)} models. Target Score: > {self.threshold}")
160
 
161
  except Exception as e:
162
  print(f"❌ [SniperEngine] Init failed: {e}")
 
164
  self.initialized = False
165
 
166
  def set_entry_threshold(self, new_threshold: float):
167
+ self.threshold = new_threshold
 
 
 
 
 
168
 
169
  def _calculate_features_live(self, df_1m: pd.DataFrame) -> pd.DataFrame:
 
 
 
170
  try:
171
  df_with_std_feats = _add_standard_features(df_1m)
172
  df_with_all_feats = _add_liquidity_proxies(df_with_std_feats)
 
177
  return pd.DataFrame()
178
 
179
  # ==============================================================================
180
+ # 📊 3. منطق تحليل دفتر الطلبات وتحويله لرقم (0.0 - 1.0)
181
  # ==============================================================================
182
+ def _score_order_book(self, order_book: Dict[str, Any]) -> Dict[str, Any]:
183
  """
184
+ تحويل حالة دفتر الطلبات إلى درجة رقمية (OB Score) من 0.0 إلى 1.0
185
  """
186
  try:
187
+ bids = order_book.get('bids', [])
188
+ asks = order_book.get('asks', [])
189
 
190
  if not bids or not asks:
191
+ return {'score': 0.0, 'imbalance': 0.0, 'wall_ratio': 0.0, 'reason': 'Empty'}
192
 
193
+ # تحليل العمق (Top 20)
194
  depth = ORDER_BOOK_DEPTH
195
  top_bids = bids[:depth]
196
  top_asks = asks[:depth]
197
 
 
198
  total_bid_vol = sum([float(x[1]) for x in top_bids])
199
  total_ask_vol = sum([float(x[1]) for x in top_asks])
200
  total_vol = total_bid_vol + total_ask_vol
201
 
202
  if total_vol == 0:
203
+ return {'score': 0.0, 'imbalance': 0.0, 'wall_ratio': 0.0, 'reason': 'Zero Vol'}
204
 
205
+ # 1. درجة عدم التوازن (Imbalance)
206
+ # تمثل نسبة المشترين (0.0 = كلهم بائعين، 1.0 = كلهم مشترين)
207
+ # هذه هي الدرجة الخام لدف��ر الطلبات
208
  bid_imbalance = total_bid_vol / total_vol
209
+
210
+ # 2. فحص الجدران (Safety Veto)
 
211
  max_ask_wall = max([float(x[1]) for x in top_asks]) if top_asks else 0
212
  ask_wall_ratio = max_ask_wall / total_ask_vol if total_ask_vol > 0 else 0
213
 
214
+ # 🛑 حق الفيتو: إذا وجد جدار بيع ضخم، الدرجة تصبح صفراً فوراً
215
+ if ask_wall_ratio >= CRITICAL_WALL_RATIO:
216
+ return {
217
+ 'score': 0.0,
218
+ 'imbalance': float(bid_imbalance),
219
+ 'wall_ratio': float(ask_wall_ratio),
220
+ 'veto': True,
221
+ 'reason': f" SELL WALL ({ask_wall_ratio:.2f})"
222
+ }
 
 
 
223
 
224
  return {
225
+ 'score': float(bid_imbalance), # الدرجة هي نسبة ضغط الشراء
226
+ 'imbalance': float(bid_imbalance),
227
+ 'wall_ratio': float(ask_wall_ratio),
228
+ 'veto': False,
229
+ 'reason': "OK"
230
  }
231
 
232
  except Exception as e:
233
+ return {'score': 0.0, 'reason': f"Error: {e}", 'veto': True}
234
 
235
  # ==============================================================================
236
+ # 🎯 4. دالة الفحص الرئيسية (The Weighted Logic 60/40)
237
  # ==============================================================================
238
  async def check_entry_signal_async(self, ohlcv_1m_data: List[List], order_book_data: Dict[str, Any] = None) -> Dict[str, Any]:
239
  """
240
+ التحقق باستخدام المعادلة الموزونة: Score = 0.6*ML + 0.4*OB
 
 
 
241
  """
242
  if not self.initialized:
243
+ return {'signal': 'WAIT', 'reason': 'Not initialized'}
244
 
245
  if len(ohlcv_1m_data) < self.LOOKBACK_WINDOW:
246
+ return {'signal': 'WAIT', 'reason': 'Insuff Data'}
247
 
248
+ # --- A. درجة النموذج (ML Score - 60%) ---
249
  try:
250
  df = pd.DataFrame(ohlcv_1m_data, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
251
  df[['open', 'high', 'low', 'close', 'volume']] = df[['open', 'high', 'low', 'close', 'volume']].astype(float)
252
 
253
  df_features = self._calculate_features_live(df)
 
254
  if df_features.empty:
255
+ return {'signal': 'WAIT', 'reason': 'Feat Fail'}
256
 
257
+ X_live = df_final = df_features.iloc[-1:][self.feature_names].fillna(0)
 
258
 
259
+ preds = [m.predict(X_live)[0][1] for m in self.models]
260
+ ml_score = float(np.mean(preds))
 
 
 
 
 
 
 
261
 
262
  except Exception as e:
263
+ print(f"❌ [Sniper] ML Error: {e}")
 
264
  return {'signal': 'WAIT', 'reason': f'ML Exception: {e}'}
265
 
266
+ # --- B. درجة دفتر الطلبات (OB Score - 40%) ---
267
+ ob_data = {'score': 0.5, 'imbalance': 0.5, 'wall_ratio': 0.0, 'veto': False} # حياد افتراضي
268
  if order_book_data:
269
+ ob_data = self._score_order_book(order_book_data)
270
 
271
+ # --- C. المعادلة الوزنية (The Formula) ---
272
 
273
+ # المعادلة التي طلبتها:
274
+ # Final Score = (0.60 * ML) + (0.40 * OB)
275
+
276
+ weight_ml = 0.60
277
+ weight_ob = 0.40
278
+
279
+ final_score = (ml_score * weight_ml) + (ob_data['score'] * weight_ob)
280
+
281
+ # --- D. اتخاذ القرار (Decision) ---
282
+
283
+ signal = 'WAIT'
284
+ reason_str = f"Final:{final_score:.2f} (ML:{ml_score:.2f} + OB:{ob_data['score']:.2f})"
285
+
286
+ # 1. فحص الفيتو أولاً (Safety First)
287
+ if ob_data.get('veto', False):
288
+ signal = 'WAIT'
289
+ reason_str = f"⛔ BLOCKED by OB Veto: {ob_data.get('reason')}"
290
+
291
+ # 2. فحص الدرجة النهائية
292
+ elif final_score >= self.threshold:
293
+ signal = 'BUY'
294
+ reason_str = f"✅ APPROVED: {final_score:.2f} >= {self.threshold} | ML:{ml_score:.2f} OB:{ob_data['score']:.2f}"
295
 
 
296
  else:
297
+ signal = 'WAIT'
298
+ reason_str = f" LOW SCORE: {final_score:.2f} < {self.threshold} | ML:{ml_score:.2f} OB:{ob_data['score']:.2f}"
299
 
300
  return {
301
+ 'signal': signal,
302
+ 'confidence_prob': final_score, # نعيد الدرجة النهائية الموزونة كدرجة ثقة
303
+ 'ml_score': ml_score,
304
+ 'ob_score': ob_data['score'],
305
+ 'ob_data': ob_data,
306
  'threshold': self.threshold,
307
+ 'reason': reason_str
 
308
  }