Riy777 commited on
Commit
dcd1343
ยท
verified ยท
1 Parent(s): 93cfa3e

Update ml_engine/sniper_engine.py

Browse files
Files changed (1) hide show
  1. ml_engine/sniper_engine.py +36 -16
ml_engine/sniper_engine.py CHANGED
@@ -1,6 +1,10 @@
1
  # ============================================================
2
  # ๐ŸŽฏ ml_engine/sniper_engine.py
3
- # (V2.1 - GEM-Architect: Multi-Exchange Unpacking Fix)
 
 
 
 
4
  # ============================================================
5
 
6
  import os
@@ -13,19 +17,23 @@ import traceback
13
  from typing import List, Dict, Any, Optional
14
 
15
  N_SPLITS = 5
16
- LOOKBACK_WINDOW = 500
 
 
17
 
18
  # ============================================================
19
  # ๐Ÿ”ง 1. Feature Engineering (Standard + Liquidity Proxies)
20
  # ============================================================
21
 
22
  def _z_score_rolling(x, w=500):
23
- r = x.rolling(w).mean()
24
- s = x.rolling(w).std().replace(0, np.nan)
 
25
  z = (x - r) / s
26
  return z.fillna(0)
27
 
28
  def _add_liquidity_proxies(df):
 
29
  df_proxy = df.copy()
30
  if 'datetime' not in df_proxy.index.names and 'timestamp' in df_proxy.columns:
31
  df_proxy['datetime'] = pd.to_datetime(df_proxy['timestamp'], unit='ms')
@@ -37,7 +45,7 @@ def _add_liquidity_proxies(df):
37
  df_proxy['amihud'] = (df_proxy['ret'].abs() / df_proxy['dollar_vol'].replace(0, np.nan)).fillna(np.inf)
38
 
39
  dp = df_proxy['close'].diff()
40
- roll_cov = dp.rolling(64).cov(dp.shift(1))
41
  df_proxy['roll_spread'] = (2 * np.sqrt(np.maximum(0, -roll_cov))).bfill()
42
 
43
  sign = np.sign(df_proxy['close'].diff()).fillna(0)
@@ -100,7 +108,7 @@ def _add_standard_features(df):
100
  return df_feat
101
 
102
  # ============================================================
103
- # ๐ŸŽฏ 2. SniperEngine Class (Refactored)
104
  # ============================================================
105
 
106
  class SniperEngine:
@@ -110,6 +118,7 @@ class SniperEngine:
110
  self.models: List[lgb.Booster] = []
111
  self.feature_names: List[str] = []
112
 
 
113
  self.entry_threshold = 0.40
114
  self.wall_ratio_limit = 0.40
115
  self.weight_ml = 0.60
@@ -126,7 +135,7 @@ class SniperEngine:
126
 
127
  self._wall_cache = {}
128
 
129
- print("๐ŸŽฏ [SniperEngine V2.1] Weighted Depth & Multi-Exchange Robustness Ready.")
130
 
131
  def configure_settings(self,
132
  threshold: float,
@@ -176,7 +185,7 @@ class SniperEngine:
176
  return pd.DataFrame()
177
 
178
  # ==============================================================================
179
- # ๐Ÿ“Š 3. Smart Order Book Logic (The Architect's Upgrade)
180
  # ==============================================================================
181
  def _score_order_book(self, order_book: Dict[str, Any], symbol: str = None) -> Dict[str, Any]:
182
  try:
@@ -186,7 +195,7 @@ class SniperEngine:
186
  if not bids or not asks:
187
  return {'score': 0.0, 'imbalance': 0.0, 'veto': True, 'reason': 'Empty OB'}
188
 
189
- # โœ… Safe Indexing Fix
190
  best_bid = float(bids[0][0])
191
  best_ask = float(asks[0][0])
192
  spread_pct = (best_ask - best_bid) / best_bid
@@ -207,7 +216,7 @@ class SniperEngine:
207
  for i in range(depth):
208
  weight = 1.0 / (1.0 + (self.ob_depth_decay * i))
209
 
210
- # โœ… Safe Indexing Fix
211
  bid_vol = float(bids[i][1])
212
  ask_vol = float(asks[i][1])
213
 
@@ -221,8 +230,7 @@ class SniperEngine:
221
  max_valid_wall = 0.0
222
  limit_price = best_ask * (1 + self.max_wall_dist)
223
 
224
- # โœ… Safe Loop Unpacking Fix
225
- # Was: for price, vol in asks[:depth]:
226
  for item in asks[:depth]:
227
  p = float(item[0])
228
  v = float(item[1])
@@ -264,6 +272,9 @@ class SniperEngine:
264
  except Exception as e:
265
  return {'score': 0.0, 'veto': True, 'reason': f"OB Error: {e}"}
266
 
 
 
 
267
  async def check_entry_signal_async(self,
268
  ohlcv_1m_data: List[List],
269
  order_book_data: Dict[str, Any] = None,
@@ -275,7 +286,9 @@ class SniperEngine:
275
  ml_score = 0.5
276
  ml_reason = "No Data"
277
 
278
- if len(ohlcv_1m_data) >= self.LOOKBACK_WINDOW and self.models:
 
 
279
  try:
280
  df = pd.DataFrame(ohlcv_1m_data, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
281
  df[['open', 'high', 'low', 'close', 'volume']] = df[['open', 'high', 'low', 'close', 'volume']].astype(float)
@@ -290,23 +303,30 @@ class SniperEngine:
290
  print(f"โŒ [Sniper] ML Error: {e}")
291
  ml_reason = "ML Err"
292
 
 
293
  ob_res = {'score': 0.5, 'imbalance': 0.5, 'veto': False, 'reason': 'No OB'}
294
  if order_book_data:
295
  ob_res = self._score_order_book(order_book_data, symbol=symbol)
296
 
 
 
 
 
297
  if ob_res.get('veto', False):
298
  final_score = 0.0
299
  signal = 'WAIT'
300
- reason_str = f"โ›” {ob_res['reason']} | {ml_reason}"
 
301
  else:
302
  final_score = (ml_score * self.weight_ml) + (ob_res['score'] * self.weight_ob)
303
 
304
  if final_score >= self.entry_threshold:
305
  signal = 'BUY'
306
- reason_str = f"โœ… GO: {final_score:.2f} | {ml_reason} | OB:{ob_res['score']:.2f}"
307
  else:
308
  signal = 'WAIT'
309
- reason_str = f"๐Ÿ“‰ Low Score: {final_score:.2f} | {ml_reason}"
 
310
 
311
  return {
312
  'signal': signal,
 
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
  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
 
35
  def _add_liquidity_proxies(df):
36
+ """ุญุณุงุจ ู…ุคุดุฑุงุช ุงู„ุณูŠูˆู„ุฉ ุงู„ู…ุชู‚ุฏู…ุฉ"""
37
  df_proxy = df.copy()
38
  if 'datetime' not in df_proxy.index.names and 'timestamp' in df_proxy.columns:
39
  df_proxy['datetime'] = pd.to_datetime(df_proxy['timestamp'], unit='ms')
 
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)
 
108
  return df_feat
109
 
110
  # ============================================================
111
+ # ๐ŸŽฏ 2. SniperEngine Class (Robust)
112
  # ============================================================
113
 
114
  class SniperEngine:
 
118
  self.models: List[lgb.Booster] = []
119
  self.feature_names: List[str] = []
120
 
121
+ # --- Configurable Thresholds ---
122
  self.entry_threshold = 0.40
123
  self.wall_ratio_limit = 0.40
124
  self.weight_ml = 0.60
 
135
 
136
  self._wall_cache = {}
137
 
138
+ print("๐ŸŽฏ [SniperEngine V2.2] High-Visibility Logic Loaded.")
139
 
140
  def configure_settings(self,
141
  threshold: float,
 
185
  return pd.DataFrame()
186
 
187
  # ==============================================================================
188
+ # ๐Ÿ“Š 3. Smart Order Book Logic (OKX Safe)
189
  # ==============================================================================
190
  def _score_order_book(self, order_book: Dict[str, Any], symbol: str = None) -> Dict[str, Any]:
191
  try:
 
195
  if not bids or not asks:
196
  return {'score': 0.0, 'imbalance': 0.0, 'veto': True, 'reason': 'Empty OB'}
197
 
198
+ # โœ… Safe Indexing
199
  best_bid = float(bids[0][0])
200
  best_ask = float(asks[0][0])
201
  spread_pct = (best_ask - best_bid) / best_bid
 
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
  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])
 
272
  except Exception as e:
273
  return {'score': 0.0, 'veto': True, 'reason': f"OB Error: {e}"}
274
 
275
+ # ==============================================================================
276
+ # ๐ŸŽฏ 4. Main Signal Check (Fixed Logging)
277
+ # ==============================================================================
278
  async def check_entry_signal_async(self,
279
  ohlcv_1m_data: List[List],
280
  order_book_data: Dict[str, Any] = None,
 
286
  ml_score = 0.5
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'])
294
  df[['open', 'high', 'low', 'close', 'volume']] = df[['open', 'high', 'low', 'close', 'volume']].astype(float)
 
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)
322
 
323
  if final_score >= self.entry_threshold:
324
  signal = 'BUY'
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 {
332
  'signal': signal,