Riy777 commited on
Commit
5bdb2c0
·
verified ·
1 Parent(s): 0a85497

Update backtest_engine.py

Browse files
Files changed (1) hide show
  1. backtest_engine.py +25 -20
backtest_engine.py CHANGED
@@ -104,8 +104,8 @@ def _transform_window_for_pattern(df_window):
104
 
105
  def calculate_sniper_features_exact(df):
106
  """
107
- Sniper Features Calculation - TRUE MATCH with 'sniper_engine.py'.
108
- Includes Liquidity Proxies & Microstructure Features.
109
  """
110
  # 1. Standard Features
111
  d = df.copy()
@@ -117,7 +117,7 @@ def calculate_sniper_features_exact(df):
117
  d['return_5m'] = c.pct_change(5).fillna(0)
118
  d['return_15m'] = c.pct_change(15).fillna(0)
119
 
120
- # Technicals (Lower Case matches model expectations)
121
  d['rsi_14'] = ta.rsi(c, length=14).fillna(50)
122
 
123
  ema_9 = ta.ema(c, length=9).fillna(c)
@@ -126,10 +126,12 @@ def calculate_sniper_features_exact(df):
126
  d['ema_9_slope'] = ((ema_9 - ema_9.shift(1)) / ema_9.shift(1).replace(0, np.nan)).fillna(0)
127
  d['ema_21_dist'] = ((c - ema_21) / ema_21.replace(0, np.nan)).fillna(0)
128
 
129
- # ATR (Length 100 as per sniper_engine.py usually)
130
- d['atr'] = ta.atr(h, l, c, length=100).fillna(0)
 
 
131
 
132
- # Volume Z-Score
133
  def _z_roll(x, w=50):
134
  r = x.rolling(w).mean()
135
  s = x.rolling(w).std().replace(0, np.nan)
@@ -137,43 +139,46 @@ def calculate_sniper_features_exact(df):
137
 
138
  d['vol_zscore_50'] = _z_roll(v, 50)
139
 
140
- # Candle Geometry
141
- rng = (h - l).replace(0, 1e-9)
142
- d['candle_range'] = h - l
143
- d['close_pos_in_range'] = ((c - l) / rng).fillna(0.5)
144
 
145
- # 2. Liquidity Proxies (The Missing 16 Features!)
146
- # Amihud Illiquidity
147
  d['dollar_vol'] = c * v
148
  d['amihud'] = (d['return_1m'].abs() / d['dollar_vol'].replace(0, np.nan)).fillna(0)
149
 
150
- # Roll Spread (Volatility Proxy)
151
  dp = c.diff()
152
  roll_cov = dp.rolling(64).cov(dp.shift(1)).fillna(0)
153
- d['roll_spread'] = (2 * np.sqrt(np.maximum(0, -roll_cov)))
 
154
 
155
- # OFI (Order Flow Imbalance Proxy)
 
 
156
  sign = np.sign(c.diff()).fillna(0)
157
  d['signed_vol'] = sign * v
158
  d['ofi'] = d['signed_vol'].rolling(30).sum().fillna(0)
159
 
160
- # VPIN Proxy
161
  buy_vol = (sign > 0) * v
162
  sell_vol = (sign < 0) * v
163
  imb = (buy_vol.rolling(60).sum() - sell_vol.rolling(60).sum()).abs()
164
  tot = v.rolling(60).sum().replace(0, np.nan)
165
  d['vpin'] = (imb / tot).fillna(0)
166
 
167
- # Garman-Klass Volatility
168
  d['rv_gk'] = ((np.log(h / l)**2) / 2) - ((2 * np.log(2) - 1) * (np.log(c / o)**2))
169
  d['rv_gk'] = d['rv_gk'].fillna(0)
170
 
171
- # VWAP Deviation
172
  vwap_win = 20
173
  vwap = (d['dollar_vol'].rolling(vwap_win).sum() / v.rolling(vwap_win).sum().replace(0, np.nan)).fillna(c)
174
- d['vwap_dev'] = (c - vwap).fillna(0)
175
 
176
- # Liquidity Score (Composite)
177
  d['L_score'] = (
178
  _z_roll(v, 500) +
179
  _z_roll(1 / (d['amihud'] + 1e-12), 500) +
 
104
 
105
  def calculate_sniper_features_exact(df):
106
  """
107
+ Sniper Features Calculation - With AUTO-NORMALIZATION Patch.
108
+ Converts price-dependent features to percentages to fix Scale Mismatch.
109
  """
110
  # 1. Standard Features
111
  d = df.copy()
 
117
  d['return_5m'] = c.pct_change(5).fillna(0)
118
  d['return_15m'] = c.pct_change(15).fillna(0)
119
 
120
+ # Technicals
121
  d['rsi_14'] = ta.rsi(c, length=14).fillna(50)
122
 
123
  ema_9 = ta.ema(c, length=9).fillna(c)
 
126
  d['ema_9_slope'] = ((ema_9 - ema_9.shift(1)) / ema_9.shift(1).replace(0, np.nan)).fillna(0)
127
  d['ema_21_dist'] = ((c - ema_21) / ema_21.replace(0, np.nan)).fillna(0)
128
 
129
+ # --- GEM-ARCHITECT PATCH: Normalize Price-Dependent Features ---
130
+ # ATR (Normalized by Close)
131
+ atr_raw = ta.atr(h, l, c, length=100).fillna(0)
132
+ d['atr'] = (atr_raw / c).fillna(0) # Now it's a percentage (e.g., 0.002 instead of 0.25)
133
 
134
+ # Volume Z-Score (Already normalized by z-score logic)
135
  def _z_roll(x, w=50):
136
  r = x.rolling(w).mean()
137
  s = x.rolling(w).std().replace(0, np.nan)
 
139
 
140
  d['vol_zscore_50'] = _z_roll(v, 50)
141
 
142
+ # Candle Geometry (Normalized)
143
+ rng = (h - l)
144
+ d['candle_range'] = (rng / c).fillna(0) # Normalized
145
+ d['close_pos_in_range'] = ((c - l) / rng.replace(0, 1e-9)).fillna(0.5)
146
 
147
+ # 2. Liquidity Proxies
148
+ # Amihud (Ratio of %Ret to $Vol -> inherently somewhat scale-invariant but sensitive)
149
  d['dollar_vol'] = c * v
150
  d['amihud'] = (d['return_1m'].abs() / d['dollar_vol'].replace(0, np.nan)).fillna(0)
151
 
152
+ # Roll Spread (Normalized)
153
  dp = c.diff()
154
  roll_cov = dp.rolling(64).cov(dp.shift(1)).fillna(0)
155
+ roll_spread_raw = (2 * np.sqrt(np.maximum(0, -roll_cov)))
156
+ d['roll_spread'] = (roll_spread_raw / c).fillna(0) # Normalized
157
 
158
+ # OFI (Volume based -> Normalized by Z-Score later or used raw if model expects raw volume flow)
159
+ # Usually OFI is used as a signal direction, kept raw or z-scored.
160
+ # Let's keep raw here as it's volume-based, not price-based.
161
  sign = np.sign(c.diff()).fillna(0)
162
  d['signed_vol'] = sign * v
163
  d['ofi'] = d['signed_vol'].rolling(30).sum().fillna(0)
164
 
165
+ # VPIN (Ratio -> Unitless -> Safe)
166
  buy_vol = (sign > 0) * v
167
  sell_vol = (sign < 0) * v
168
  imb = (buy_vol.rolling(60).sum() - sell_vol.rolling(60).sum()).abs()
169
  tot = v.rolling(60).sum().replace(0, np.nan)
170
  d['vpin'] = (imb / tot).fillna(0)
171
 
172
+ # Garman-Klass (Log returns -> Unitless -> Safe)
173
  d['rv_gk'] = ((np.log(h / l)**2) / 2) - ((2 * np.log(2) - 1) * (np.log(c / o)**2))
174
  d['rv_gk'] = d['rv_gk'].fillna(0)
175
 
176
+ # VWAP Deviation (Normalized)
177
  vwap_win = 20
178
  vwap = (d['dollar_vol'].rolling(vwap_win).sum() / v.rolling(vwap_win).sum().replace(0, np.nan)).fillna(c)
179
+ d['vwap_dev'] = ((c - vwap) / c).fillna(0) # Normalized
180
 
181
+ # Liquidity Score (Composite - Z-scores handle scaling automatically)
182
  d['L_score'] = (
183
  _z_roll(v, 500) +
184
  _z_roll(1 / (d['amihud'] + 1e-12), 500) +