Riy777 commited on
Commit
48be821
ยท
verified ยท
1 Parent(s): dcd1343

Update ml_engine/sniper_engine.py

Browse files
Files changed (1) hide show
  1. ml_engine/sniper_engine.py +18 -20
ml_engine/sniper_engine.py CHANGED
@@ -1,10 +1,10 @@
1
  # ============================================================
2
  # ๐ŸŽฏ ml_engine/sniper_engine.py
3
- # (V2.2 - GEM-Architect: Full Visibility & OKX Patch)
4
  # ============================================================
5
- # - Fixed: Logs now ALWAYS show ML and OB scores (Pass or Fail).
6
- # - Fixed: Data requirements relaxed to prevent 'No Data' on minor gaps.
7
- # - Integrity: OKX/Binance Unpacking Logic Preserved.
8
  # ============================================================
9
 
10
  import os
@@ -17,18 +17,21 @@ import traceback
17
  from typing import List, Dict, Any, Optional
18
 
19
  N_SPLITS = 5
20
- # ุชู… ุชุฎููŠู ุงู„ุดุฑุท ู‡ู†ุง ู„ู„ุณู…ุงุญ ู„ู„ู…ูˆุฏูŠู„ ุจุงู„ุนู…ู„ ุญุชู‰ ู„ูˆ ุงู„ุจูŠุงู†ุงุช ู†ุงู‚ุตุฉ ู‚ู„ูŠู„ุงู‹
21
- # ุงู„ู…ูˆุฏูŠู„ ุณูŠุชุนุงู…ู„ ู…ุน ุงู„ู€ NaN ุจุดูƒู„ ุทุจูŠุนูŠ
22
- LOOKBACK_WINDOW = 200
23
 
24
  # ============================================================
25
  # ๐Ÿ”ง 1. Feature Engineering (Standard + Liquidity Proxies)
26
  # ============================================================
27
 
28
  def _z_score_rolling(x, w=500):
29
- # Use min_periods to allow calculation even with less data
30
- r = x.rolling(w, min_periods=100).mean()
31
- s = x.rolling(w, min_periods=100).std().replace(0, np.nan)
 
 
 
 
32
  z = (x - r) / s
33
  return z.fillna(0)
34
 
@@ -45,7 +48,8 @@ def _add_liquidity_proxies(df):
45
  df_proxy['amihud'] = (df_proxy['ret'].abs() / df_proxy['dollar_vol'].replace(0, np.nan)).fillna(np.inf)
46
 
47
  dp = df_proxy['close'].diff()
48
- roll_cov = dp.rolling(64, min_periods=30).cov(dp.shift(1))
 
49
  df_proxy['roll_spread'] = (2 * np.sqrt(np.maximum(0, -roll_cov))).bfill()
50
 
51
  sign = np.sign(df_proxy['close'].diff()).fillna(0)
@@ -100,6 +104,8 @@ def _add_standard_features(df):
100
  df_feat['ema_21_dist'] = 0
101
 
102
  df_feat['atr'] = ta.atr(df_feat['high'], df_feat['low'], df_feat['close'], length=100)
 
 
103
  df_feat['vol_zscore_50'] = _z_score_rolling(df_feat['volume'], w=50)
104
 
105
  df_feat['candle_range'] = df_feat['high'] - df_feat['low']
@@ -135,7 +141,7 @@ class SniperEngine:
135
 
136
  self._wall_cache = {}
137
 
138
- print("๐ŸŽฏ [SniperEngine V2.2] High-Visibility Logic Loaded.")
139
 
140
  def configure_settings(self,
141
  threshold: float,
@@ -216,7 +222,6 @@ class SniperEngine:
216
  for i in range(depth):
217
  weight = 1.0 / (1.0 + (self.ob_depth_decay * i))
218
 
219
- # โœ… Safe Indexing
220
  bid_vol = float(bids[i][1])
221
  ask_vol = float(asks[i][1])
222
 
@@ -230,7 +235,6 @@ class SniperEngine:
230
  max_valid_wall = 0.0
231
  limit_price = best_ask * (1 + self.max_wall_dist)
232
 
233
- # โœ… Safe Unpacking Loop
234
  for item in asks[:depth]:
235
  p = float(item[0])
236
  v = float(item[1])
@@ -287,7 +291,6 @@ class SniperEngine:
287
  ml_reason = "No Data"
288
 
289
  # โœ… Relaxed Check: Allow partial data (min 100) instead of strict 500
290
- # LightGBM handles NaNs, better to run with partial data than show 'No Data'
291
  if len(ohlcv_1m_data) >= 100 and self.models:
292
  try:
293
  df = pd.DataFrame(ohlcv_1m_data, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
@@ -303,19 +306,15 @@ class SniperEngine:
303
  print(f"โŒ [Sniper] ML Error: {e}")
304
  ml_reason = "ML Err"
305
 
306
- # --- Smart Order Book Analysis ---
307
  ob_res = {'score': 0.5, 'imbalance': 0.5, 'veto': False, 'reason': 'No OB'}
308
  if order_book_data:
309
  ob_res = self._score_order_book(order_book_data, symbol=symbol)
310
 
311
- # โœ… FIXED: Prepare Score Strings for Visibility in ALL cases
312
  ob_str = f"OB:{ob_res['score']:.2f}"
313
 
314
- # --- Final Hybrid Score ---
315
  if ob_res.get('veto', False):
316
  final_score = 0.0
317
  signal = 'WAIT'
318
- # โœ… Added ml_reason to veto log
319
  reason_str = f"โ›” {ob_res['reason']} | {ml_reason} | {ob_str}"
320
  else:
321
  final_score = (ml_score * self.weight_ml) + (ob_res['score'] * self.weight_ob)
@@ -325,7 +324,6 @@ class SniperEngine:
325
  reason_str = f"โœ… GO: {final_score:.2f} | {ml_reason} | {ob_str}"
326
  else:
327
  signal = 'WAIT'
328
- # โœ… ADDED: Missing Info Restored here
329
  reason_str = f"๐Ÿ“‰ Low Score: {final_score:.2f} | {ml_reason} | {ob_str}"
330
 
331
  return {
 
1
  # ============================================================
2
  # ๐ŸŽฏ ml_engine/sniper_engine.py
3
+ # (V2.3 - GEM-Architect: Dynamic Window Fix)
4
  # ============================================================
5
+ # - Fixed: Rolling window error (min_periods > window).
6
+ # - Logic: Auto-adjusts min_periods to fit requested window size.
7
+ # - Integrity: Full ML & OB functionality restored.
8
  # ============================================================
9
 
10
  import os
 
17
  from typing import List, Dict, Any, Optional
18
 
19
  N_SPLITS = 5
20
+ # ุงู„ุณู…ุงุญ ุจุจูŠุงู†ุงุช ุฃู‚ู„ ู„ู„ู…ุฑูˆู†ุฉ (ุจุฏู„ุงู‹ ู…ู† 500)
21
+ LOOKBACK_WINDOW = 150
 
22
 
23
  # ============================================================
24
  # ๐Ÿ”ง 1. Feature Engineering (Standard + Liquidity Proxies)
25
  # ============================================================
26
 
27
  def _z_score_rolling(x, w=500):
28
+ # โœ… FIX: Ensure min_periods never exceeds window size (w)
29
+ # If w=50 (like in vol_zscore), min_periods becomes 50.
30
+ # If w=500, min_periods becomes 100 (allowing partial calculation).
31
+ effective_min = min(w, 100)
32
+
33
+ r = x.rolling(w, min_periods=effective_min).mean()
34
+ s = x.rolling(w, min_periods=effective_min).std().replace(0, np.nan)
35
  z = (x - r) / s
36
  return z.fillna(0)
37
 
 
48
  df_proxy['amihud'] = (df_proxy['ret'].abs() / df_proxy['dollar_vol'].replace(0, np.nan)).fillna(np.inf)
49
 
50
  dp = df_proxy['close'].diff()
51
+ # Reduced min_periods for reliability
52
+ roll_cov = dp.rolling(64, min_periods=20).cov(dp.shift(1))
53
  df_proxy['roll_spread'] = (2 * np.sqrt(np.maximum(0, -roll_cov))).bfill()
54
 
55
  sign = np.sign(df_proxy['close'].diff()).fillna(0)
 
104
  df_feat['ema_21_dist'] = 0
105
 
106
  df_feat['atr'] = ta.atr(df_feat['high'], df_feat['low'], df_feat['close'], length=100)
107
+
108
+ # This was causing the error (w=50 vs min=100). Now fixed in helper.
109
  df_feat['vol_zscore_50'] = _z_score_rolling(df_feat['volume'], w=50)
110
 
111
  df_feat['candle_range'] = df_feat['high'] - df_feat['low']
 
141
 
142
  self._wall_cache = {}
143
 
144
+ print("๐ŸŽฏ [SniperEngine V2.3] Dynamic Window Logic Loaded.")
145
 
146
  def configure_settings(self,
147
  threshold: float,
 
222
  for i in range(depth):
223
  weight = 1.0 / (1.0 + (self.ob_depth_decay * i))
224
 
 
225
  bid_vol = float(bids[i][1])
226
  ask_vol = float(asks[i][1])
227
 
 
235
  max_valid_wall = 0.0
236
  limit_price = best_ask * (1 + self.max_wall_dist)
237
 
 
238
  for item in asks[:depth]:
239
  p = float(item[0])
240
  v = float(item[1])
 
291
  ml_reason = "No Data"
292
 
293
  # โœ… Relaxed Check: Allow partial data (min 100) instead of strict 500
 
294
  if len(ohlcv_1m_data) >= 100 and self.models:
295
  try:
296
  df = pd.DataFrame(ohlcv_1m_data, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
 
306
  print(f"โŒ [Sniper] ML Error: {e}")
307
  ml_reason = "ML Err"
308
 
 
309
  ob_res = {'score': 0.5, 'imbalance': 0.5, 'veto': False, 'reason': 'No OB'}
310
  if order_book_data:
311
  ob_res = self._score_order_book(order_book_data, symbol=symbol)
312
 
 
313
  ob_str = f"OB:{ob_res['score']:.2f}"
314
 
 
315
  if ob_res.get('veto', False):
316
  final_score = 0.0
317
  signal = 'WAIT'
 
318
  reason_str = f"โ›” {ob_res['reason']} | {ml_reason} | {ob_str}"
319
  else:
320
  final_score = (ml_score * self.weight_ml) + (ob_res['score'] * self.weight_ob)
 
324
  reason_str = f"โœ… GO: {final_score:.2f} | {ml_reason} | {ob_str}"
325
  else:
326
  signal = 'WAIT'
 
327
  reason_str = f"๐Ÿ“‰ Low Score: {final_score:.2f} | {ml_reason} | {ob_str}"
328
 
329
  return {