Riy777 commited on
Commit
8376ab1
·
verified ·
1 Parent(s): 1e0d3b2

Update backtest_engine.py

Browse files
Files changed (1) hide show
  1. backtest_engine.py +53 -110
backtest_engine.py CHANGED
@@ -1,5 +1,5 @@
1
  # ============================================================
2
- # 🧪 backtest_engine.py (V122.0 - GEM-Architect: Full Spectrum)
3
  # ============================================================
4
 
5
  import asyncio
@@ -34,10 +34,10 @@ logging.getLogger('ml_engine').setLevel(logging.WARNING)
34
  CACHE_DIR = "backtest_real_scores"
35
 
36
  # ============================================================
37
- # 🔧 1. PATTERN ENGINE LOGIC (EXACT REPLICA)
38
  # ============================================================
 
39
  def _zv(x):
40
- """Z-Score Normalization (Safe)"""
41
  with np.errstate(divide='ignore', invalid='ignore'):
42
  x = np.asarray(x, dtype="float32")
43
  m = np.nanmean(x, axis=0)
@@ -45,39 +45,27 @@ def _zv(x):
45
  return np.nan_to_num((x - m) / s, nan=0.0)
46
 
47
  def _transform_window_for_pattern(df_window):
48
- """
49
- تحويل نافذة (200 شمعة) إلى متجه ميزات كما في patterns.py تماماً.
50
- """
51
  try:
52
- # Extract numpy arrays
53
  c = df_window['close'].values.astype('float32')
54
  o = df_window['open'].values.astype('float32')
55
  h = df_window['high'].values.astype('float32')
56
  l = df_window['low'].values.astype('float32')
57
  v = df_window['volume'].values.astype('float32')
58
 
59
- # 1. Base Features
60
- base = np.stack([o, h, l, c, v], axis=1) # (200, 5)
61
  base_z = _zv(base)
62
 
63
- # 2. Extra
64
  lr = np.zeros_like(c); lr[1:] = np.diff(np.log1p(c))
65
  rng = (h - l) / (c + 1e-9)
66
  extra = np.stack([lr, rng], axis=1)
67
  extra_z = _zv(extra)
68
 
69
- # 3. Indicators (Simplified Calculation for Speed using TA-Lib vectorization if possible, else manual)
70
- # Using pandas_ta on small window is slow. We use numpy optimized versions.
71
- # EMA
72
  def _ema(arr, n):
73
- alpha = 2/(n+1)
74
- # Fast recursive implementation or pandas ewm
75
  return pd.Series(arr).ewm(span=n, adjust=False).mean().values
76
 
77
  ema9 = _ema(c, 9); ema21 = _ema(c, 21); ema50 = _ema(c, 50); ema200 = _ema(c, 200)
78
  slope21 = np.gradient(ema21); slope50 = np.gradient(ema50)
79
 
80
- # RSI (Numpy version approx)
81
  delta = np.diff(c, prepend=c[0])
82
  up, down = delta.copy(), delta.copy()
83
  up[up < 0] = 0; down[down > 0] = 0
@@ -86,30 +74,18 @@ def _transform_window_for_pattern(df_window):
86
  rs = roll_up / (roll_down + 1e-9)
87
  rsi = 100.0 - (100.0 / (1.0 + rs))
88
 
89
- # ATR & BB & OBV (Simplified for speed match)
90
- # ... (Assuming simplified indicators for pattern matching structure)
91
-
92
- # Stack indicators
93
  indicators = np.stack([ema9, ema21, ema50, ema200, slope21, slope50, rsi], axis=1)
94
- # Padding to match 12 features if needed (Patterns.py has 12)
95
- # We fill zeros for heavy ones like MACD/OBV to save time, relying on EMA structure
96
  padding = np.zeros((200, 5), dtype='float32')
97
  indicators_full = np.concatenate([indicators, padding], axis=1)
98
  indicators_z = _zv(indicators_full)
99
 
100
- # 4. Flatten
101
- X_seq = np.concatenate([base_z, extra_z, indicators_z], axis=1) # (200, Features)
102
  X_flat = X_seq.flatten()
103
-
104
- # 5. Stat (MC/Hurst - Mocked for speed as 0.5)
105
  X_stat = np.array([0.5, 0.0, 0.5], dtype="float32")
106
 
107
  return np.concatenate([X_flat, X_stat])
108
  except: return None
109
 
110
- # ============================================================
111
- # 🔧 2. SNIPER FEATURES (EXACT REPLICA)
112
- # ============================================================
113
  def _z_score_rolling(x, w=500):
114
  r = x.rolling(w).mean(); s = x.rolling(w).std().replace(0, np.nan); return ((x - r) / s).fillna(0)
115
 
@@ -150,9 +126,6 @@ def calculate_sniper_features_exact(df):
150
 
151
  return df.fillna(0)
152
 
153
- # ============================================================
154
- # 🔧 3. TITAN FEATURES (REAL)
155
- # ============================================================
156
  def calculate_titan_features_real(df):
157
  df = df.copy()
158
  df['RSI'] = ta.rsi(df['close'], 14)
@@ -191,29 +164,47 @@ class HeavyDutyBacktester:
191
  for f in glob.glob(os.path.join(CACHE_DIR, "*")): os.remove(f)
192
  else: os.makedirs(CACHE_DIR)
193
 
194
- print(f"🧪 [Backtest V122.0] Full Spectrum Mode. Checking Engines...")
195
  self._check_engines()
196
 
197
  def _check_engines(self):
198
  status = []
199
- if self.proc.titan and self.proc.titan.model: status.append("Titan")
200
- if self.proc.pattern_engine and self.proc.pattern_engine.models: status.append("Patterns")
201
- else: print("⚠️ Pattern Models Missing (Will use neutral)")
202
- if self.proc.oracle: status.append("Oracle")
203
- if self.proc.sniper: status.append("Sniper")
204
- if self.proc.guardian_hydra: status.append("Hydra")
 
205
  print(f" ✅ Engines Ready: {', '.join(status)}")
206
 
207
  def set_date_range(self, start_str, end_str):
208
  self.force_start_date = start_str; self.force_end_date = end_str
209
 
 
210
  def _smart_predict(self, model, X):
 
211
  try:
 
212
  if hasattr(model, "predict_proba"):
213
  raw = model.predict_proba(X)
214
- return raw[:, -1] if raw.ndim == 2 else raw
 
 
 
 
215
  return model.predict(X)
216
- except: return np.zeros(len(X))
 
 
 
 
 
 
 
 
 
 
217
 
218
  async def _fetch_all_data_fast(self, sym, start_ms, end_ms):
219
  print(f" ⚡ [Network] Downloading {sym}...", flush=True)
@@ -257,9 +248,7 @@ class HeavyDutyBacktester:
257
  df_1m.set_index('datetime', inplace=True)
258
  df_1m = df_1m.sort_index()
259
 
260
- # -----------------------------------------------------------
261
  # 1️⃣ SNIPER & L1 SCORING (L4/L1)
262
- # -----------------------------------------------------------
263
  df_sniper = calculate_sniper_features_exact(df_1m)
264
  df_sniper['rel_vol'] = df_sniper['volume'] / (df_sniper['volume'].rolling(50).mean() + 1e-9)
265
  df_sniper['l1_score'] = (df_sniper['rel_vol'] * 10) + ((df_sniper['atr']/df_sniper['close']) * 1000)
@@ -270,71 +259,45 @@ class HeavyDutyBacktester:
270
 
271
  print(f" 🎯 Candidates: {len(df_candidates)}. Running Deep Inference...", flush=True)
272
 
273
- # -----------------------------------------------------------
274
- # 2️⃣ PATTERNS (L2) - RUN ON 15M -> MAP TO 1M
275
- # -----------------------------------------------------------
276
  res_patterns = np.full(len(df_candidates), 0.5)
277
  pattern_models = getattr(self.proc.pattern_engine, 'models', {})
278
-
279
  if pattern_models and '15m' in pattern_models:
280
  try:
281
- # Resample to 15m
282
  df_15m = df_1m.resample('15T').agg({'open':'first', 'high':'max', 'low':'min', 'close':'last', 'volume':'sum'}).dropna()
283
-
284
- # Create Windows (200 candles)
285
- # Need to iterate efficiently
286
  pat_scores_15m = np.full(len(df_15m), 0.5)
287
- # Only compute for 15m candles that cover candidate times
288
-
289
- # Simplified: Loop through 15m (8k candles) is fast enough
290
- pat_inputs = []
291
- valid_15m_idxs = []
292
 
293
  for i in range(200, len(df_15m)):
294
  window = df_15m.iloc[i-200:i]
295
  vec = _transform_window_for_pattern(window)
296
  if vec is not None:
297
- pat_inputs.append(vec)
298
- valid_15m_idxs.append(i)
299
 
300
  if pat_inputs:
301
  X_pat = np.array(pat_inputs)
302
  pat_preds = self._smart_predict(pattern_models['15m'], xgb.DMatrix(X_pat))
303
  pat_scores_15m[valid_15m_idxs] = pat_preds
304
 
305
- # Map 15m scores back to candidates (Forward Fill)
306
- cand_ts = df_candidates['timestamp'].values
307
- # 15m timestamps (end of candle approx)
308
  ts_15m = df_15m.index.astype(np.int64) // 10**6
309
-
310
- # Searchsorted to find last closed 15m candle
311
- map_idxs = np.searchsorted(ts_15m, cand_ts) - 1
312
- map_idxs = np.clip(map_idxs, 0, len(pat_scores_15m)-1)
313
- res_patterns = pat_scores_15m[map_idxs]
314
-
315
  except Exception as e: print(f"Patterns Error: {e}")
316
 
317
- # -----------------------------------------------------------
318
- # 3️⃣ TITAN (L2) - RESAMPLE 5M
319
- # -----------------------------------------------------------
320
  res_titan = np.full(len(df_candidates), 0.5)
321
  if self.proc.titan and self.proc.titan.model:
322
  try:
323
  df_5m = df_1m.resample('5T').agg({'open':'first', 'high':'max', 'low':'min', 'close':'last', 'volume':'sum'}).dropna()
324
  df_5m_feat = calculate_titan_features_real(df_5m).add_prefix('5m_')
325
-
326
- cand_ts = df_candidates['timestamp'].values
327
  ts_5m = df_5m.index.astype(np.int64) // 10**6
328
- map_idxs = np.clip(np.searchsorted(ts_5m, cand_ts) - 1, 0, len(df_5m_feat)-1)
329
-
330
  feats = self.proc.titan.feature_names
331
  X_titan = df_5m_feat.iloc[map_idxs].reindex(columns=feats, fill_value=0).values
332
  res_titan = self.proc.titan.model.predict(xgb.DMatrix(X_titan, feature_names=feats))
333
  except Exception as e: print(f"Titan Error: {e}")
334
 
335
- # -----------------------------------------------------------
336
  # 4️⃣ SNIPER (L4)
337
- # -----------------------------------------------------------
338
  res_sniper = np.full(len(df_candidates), 0.5)
339
  sniper_models = getattr(self.proc.sniper, 'models', [])
340
  if sniper_models:
@@ -343,28 +306,17 @@ class HeavyDutyBacktester:
343
  for f in feats:
344
  if f not in df_candidates.columns: df_candidates[f] = 0.0
345
  X_snip = df_candidates[feats].values
 
346
  preds = [self._extract_probs(self._smart_predict(m, X_snip)) for m in sniper_models]
347
  res_sniper = np.mean(preds, axis=0)
348
  except Exception as e: print(f"Sniper Error: {e}")
349
 
350
- # -----------------------------------------------------------
351
  # 5️⃣ ORACLE (L3)
352
- # -----------------------------------------------------------
353
  res_oracle = np.full(len(df_candidates), 0.5)
354
  oracle_model = getattr(self.proc.oracle, 'model_direction', None)
355
  if oracle_model:
356
  try:
357
- # We reuse res_titan and res_patterns here!
358
  oracle_feats = getattr(self.proc.oracle, 'feature_cols', [])
359
- # Placeholder HTF logic (reusing L2/L4 data for speed as 'best effort')
360
- # In real system, Processor gathers these. Here we simulate the gather.
361
- # Titan Score -> 'sim_titan_score'
362
- # Pattern Score -> 'sim_pattern_score'
363
-
364
- # Build Oracle Vector
365
- # We cheat slightly by filling HTF cols with zeros (neutral) or proxies
366
- # because fetching full 1h/4h features for 128k candles is too heavy.
367
- # But we provide the Critical Scores.
368
  X_orc_df = pd.DataFrame(0.0, index=range(len(df_candidates)), columns=oracle_feats)
369
  if 'sim_titan_score' in X_orc_df: X_orc_df['sim_titan_score'] = res_titan
370
  if 'sim_pattern_score' in X_orc_df: X_orc_df['sim_pattern_score'] = res_patterns
@@ -373,15 +325,11 @@ class HeavyDutyBacktester:
373
  res_oracle = self._extract_probs(self._smart_predict(oracle_model, X_orc_df.values))
374
  except Exception as e: print(f"Oracle Error: {e}")
375
 
376
- # -----------------------------------------------------------
377
  # 6️⃣ HYDRA & LEGACY (L0)
378
- # -----------------------------------------------------------
379
  res_hydra_risk = np.zeros(len(df_candidates)); res_hydra_time = np.zeros(len(df_candidates), dtype=int)
380
- res_legacy_risk = np.zeros(len(df_candidates))
381
 
382
  hydra_models = getattr(self.proc.guardian_hydra, 'models', {})
383
  if hydra_models and 'crash' in hydra_models:
384
- # Sliding window approach
385
  try:
386
  global_hydra_feats = np.column_stack([
387
  df_sniper['rsi_14'], df_sniper['rsi_14'], df_sniper['rsi_14'],
@@ -390,14 +338,11 @@ class HeavyDutyBacktester:
390
  ]).astype(np.float32)
391
 
392
  window_view = sliding_window_view(global_hydra_feats, 240, axis=0).transpose(0, 2, 1)
393
-
394
- # Map indices
395
  c_idxs = np.searchsorted(df_sniper.index, df_candidates.index)
396
  valid_s = c_idxs + 1
397
  valid_mask_h = valid_s < (len(global_hydra_feats) - 240)
398
  final_s = valid_s[valid_mask_h]; res_idxs = np.where(valid_mask_h)[0]
399
 
400
- # Batch Loop
401
  for i in range(0, len(final_s), 5000):
402
  b_idxs = final_s[i:i+5000]; r_idxs = res_idxs[i:i+5000]
403
  static = window_view[b_idxs]
@@ -411,7 +356,6 @@ class HeavyDutyBacktester:
411
  max_pnl = (np.maximum.accumulate(s_c, axis=1) - entry)/dist
412
  atr_p = s_atr/(s_c+1e-9)
413
 
414
- # Stack
415
  zeros = np.zeros((B, 240)); ones = np.ones((B, 240)); t = np.tile(np.arange(1, 241), (B, 1))
416
  X = np.stack([
417
  static[:,0], static[:,1], static[:,2], static[:,3], static[:,4],
@@ -422,9 +366,7 @@ class HeavyDutyBacktester:
422
  res_hydra_risk[r_idxs] = np.max(preds, axis=1)
423
  except: pass
424
 
425
- # -----------------------------------------------------------
426
  # 7️⃣ FINAL ASSEMBLY
427
- # -----------------------------------------------------------
428
  print(f" 📊 [Stats] Titan:{res_titan.mean():.2f} | Patterns:{res_patterns.mean():.2f} | Sniper:{res_sniper.mean():.2f} | Oracle:{res_oracle.mean():.2f}")
429
 
430
  ai_df = pd.DataFrame({
@@ -437,7 +379,7 @@ class HeavyDutyBacktester:
437
  'l1_score': df_candidates['l1_score'],
438
  'risk_hydra_crash': res_hydra_risk,
439
  'time_hydra_crash': res_hydra_time,
440
- 'risk_legacy_v2': res_legacy_risk,
441
  'time_legacy_panic': 0
442
  })
443
 
@@ -493,9 +435,6 @@ class HeavyDutyBacktester:
493
  # Exit
494
  if s in pos:
495
  entry = pos[s][0]; h_r = pos[s][1]
496
- # Check hydra risk vs config thresh (dynamic exit)
497
- # For backtest, simplified: if risk > thresh AND current PnL < -1% (validation)
498
- # Or just risk > thresh
499
  crash = (h_r > cfg['hydra_thresh'])
500
  pnl = (p - entry)/entry
501
 
@@ -526,21 +465,24 @@ class HeavyDutyBacktester:
526
  await self.generate_truth_data()
527
 
528
  # Grid
529
- # Using wider ranges to ensure we find *something*
530
  oracle_r = np.linspace(0.3, 0.7, 3); sniper_r = np.linspace(0.2, 0.6, 3)
531
  hydra_r = [0.8, 0.9]; l1_r = [5.0, 10.0]
532
 
533
  combos = []
534
  for o, s, h, l1 in itertools.product(oracle_r, sniper_r, hydra_r, l1_r):
535
  combos.append({
536
- 'w_titan': 0.4, 'w_struct': 0.3, # Fixed weights for now
537
- 'l1_thresh': l1, 'oracle_thresh': o, 'sniper_thresh': s, 'hydra_thresh': h
538
  })
539
 
540
  files = glob.glob(os.path.join(CACHE_DIR, "*.pkl"))
541
  best_res = self._worker_optimize(combos, files, self.INITIAL_CAPITAL, self.TRADING_FEES, self.MAX_SLOTS)
542
- if not best_res: return None, None
543
 
 
 
 
 
 
544
  print("\n" + "="*60)
545
  print(f"🏆 CHAMPION REPORT [{target_regime}]:")
546
  print(f" 💰 Final Balance: ${best['final_balance']:,.2f}")
@@ -559,7 +501,6 @@ class HeavyDutyBacktester:
559
  print(f" ⚖️ Weights: Titan={best['config']['w_titan']:.2f} | Patterns={best['config']['w_struct']:.2f} | L1={best['config']['l1_thresh']}")
560
  print("="*60)
561
  return best['config'], best
562
-
563
  async def run_strategic_optimization_task():
564
  print("\n🧪 [STRATEGIC BACKTEST] Full Spectrum Mode...")
565
  r2 = R2Service(); dm = DataManager(None, None, r2); proc = MLProcessor(dm)
@@ -570,9 +511,11 @@ async def run_strategic_optimization_task():
570
  opt = HeavyDutyBacktester(dm, proc)
571
 
572
  scenarios = [
573
- {"regime": "BULL", "start": "2024-01-01", "end": "2024-03-30"},
574
- # Add others...
575
- ]
 
 
576
 
577
  for s in scenarios:
578
  opt.set_date_range(s["start"], s["end"])
 
1
  # ============================================================
2
+ # 🧪 backtest_engine.py (V122.1 - GEM-Architect: Method Scope Fix)
3
  # ============================================================
4
 
5
  import asyncio
 
34
  CACHE_DIR = "backtest_real_scores"
35
 
36
  # ============================================================
37
+ # 🔧 1. FEATURE ENGINEERING REPLICAS (GLOBAL SCOPE)
38
  # ============================================================
39
+
40
  def _zv(x):
 
41
  with np.errstate(divide='ignore', invalid='ignore'):
42
  x = np.asarray(x, dtype="float32")
43
  m = np.nanmean(x, axis=0)
 
45
  return np.nan_to_num((x - m) / s, nan=0.0)
46
 
47
  def _transform_window_for_pattern(df_window):
 
 
 
48
  try:
 
49
  c = df_window['close'].values.astype('float32')
50
  o = df_window['open'].values.astype('float32')
51
  h = df_window['high'].values.astype('float32')
52
  l = df_window['low'].values.astype('float32')
53
  v = df_window['volume'].values.astype('float32')
54
 
55
+ base = np.stack([o, h, l, c, v], axis=1)
 
56
  base_z = _zv(base)
57
 
 
58
  lr = np.zeros_like(c); lr[1:] = np.diff(np.log1p(c))
59
  rng = (h - l) / (c + 1e-9)
60
  extra = np.stack([lr, rng], axis=1)
61
  extra_z = _zv(extra)
62
 
 
 
 
63
  def _ema(arr, n):
 
 
64
  return pd.Series(arr).ewm(span=n, adjust=False).mean().values
65
 
66
  ema9 = _ema(c, 9); ema21 = _ema(c, 21); ema50 = _ema(c, 50); ema200 = _ema(c, 200)
67
  slope21 = np.gradient(ema21); slope50 = np.gradient(ema50)
68
 
 
69
  delta = np.diff(c, prepend=c[0])
70
  up, down = delta.copy(), delta.copy()
71
  up[up < 0] = 0; down[down > 0] = 0
 
74
  rs = roll_up / (roll_down + 1e-9)
75
  rsi = 100.0 - (100.0 / (1.0 + rs))
76
 
 
 
 
 
77
  indicators = np.stack([ema9, ema21, ema50, ema200, slope21, slope50, rsi], axis=1)
 
 
78
  padding = np.zeros((200, 5), dtype='float32')
79
  indicators_full = np.concatenate([indicators, padding], axis=1)
80
  indicators_z = _zv(indicators_full)
81
 
82
+ X_seq = np.concatenate([base_z, extra_z, indicators_z], axis=1)
 
83
  X_flat = X_seq.flatten()
 
 
84
  X_stat = np.array([0.5, 0.0, 0.5], dtype="float32")
85
 
86
  return np.concatenate([X_flat, X_stat])
87
  except: return None
88
 
 
 
 
89
  def _z_score_rolling(x, w=500):
90
  r = x.rolling(w).mean(); s = x.rolling(w).std().replace(0, np.nan); return ((x - r) / s).fillna(0)
91
 
 
126
 
127
  return df.fillna(0)
128
 
 
 
 
129
  def calculate_titan_features_real(df):
130
  df = df.copy()
131
  df['RSI'] = ta.rsi(df['close'], 14)
 
164
  for f in glob.glob(os.path.join(CACHE_DIR, "*")): os.remove(f)
165
  else: os.makedirs(CACHE_DIR)
166
 
167
+ print(f"🧪 [Backtest V122.1] Method Scope Fix. Checking Engines...")
168
  self._check_engines()
169
 
170
  def _check_engines(self):
171
  status = []
172
+ if self.proc.titan and self.proc.titan.model: status.append("Titan(Real)")
173
+ else: print("❌ Titan Model MISSING")
174
+ if self.proc.pattern_engine and self.proc.pattern_engine.models: status.append("Patterns(Real)")
175
+ else: print("⚠️ Pattern Models MISSING")
176
+ if self.proc.oracle: status.append("Oracle(Real)")
177
+ if self.proc.sniper: status.append("Sniper(Real)")
178
+ if self.proc.guardian_hydra: status.append("Hydra(Real)")
179
  print(f" ✅ Engines Ready: {', '.join(status)}")
180
 
181
  def set_date_range(self, start_str, end_str):
182
  self.force_start_date = start_str; self.force_end_date = end_str
183
 
184
+ # ✅ FIXED: Helper methods are now INSIDE the class
185
  def _smart_predict(self, model, X):
186
+ """Forces predict_proba and handles shape mismatch"""
187
  try:
188
+ # 1. Try Proba (For Classifiers)
189
  if hasattr(model, "predict_proba"):
190
  raw = model.predict_proba(X)
191
+ if raw.ndim == 2:
192
+ return raw[:, -1] # Positive/Buy Class
193
+ return raw
194
+
195
+ # 2. Fallback to predict
196
  return model.predict(X)
197
+ except:
198
+ return np.zeros(len(X) if hasattr(X, '__len__') else 0)
199
+
200
+ def _extract_probs(self, raw_preds):
201
+ """Standardizes output to 1D probability array"""
202
+ if isinstance(raw_preds, list): raw_preds = np.array(raw_preds)
203
+ if raw_preds.ndim == 1: return raw_preds
204
+ elif raw_preds.ndim == 2:
205
+ if raw_preds.shape[1] >= 2: return raw_preds[:, -1]
206
+ return raw_preds.flatten()
207
+ return raw_preds.flatten()
208
 
209
  async def _fetch_all_data_fast(self, sym, start_ms, end_ms):
210
  print(f" ⚡ [Network] Downloading {sym}...", flush=True)
 
248
  df_1m.set_index('datetime', inplace=True)
249
  df_1m = df_1m.sort_index()
250
 
 
251
  # 1️⃣ SNIPER & L1 SCORING (L4/L1)
 
252
  df_sniper = calculate_sniper_features_exact(df_1m)
253
  df_sniper['rel_vol'] = df_sniper['volume'] / (df_sniper['volume'].rolling(50).mean() + 1e-9)
254
  df_sniper['l1_score'] = (df_sniper['rel_vol'] * 10) + ((df_sniper['atr']/df_sniper['close']) * 1000)
 
259
 
260
  print(f" 🎯 Candidates: {len(df_candidates)}. Running Deep Inference...", flush=True)
261
 
262
+ # 2️⃣ PATTERNS (L2)
 
 
263
  res_patterns = np.full(len(df_candidates), 0.5)
264
  pattern_models = getattr(self.proc.pattern_engine, 'models', {})
 
265
  if pattern_models and '15m' in pattern_models:
266
  try:
 
267
  df_15m = df_1m.resample('15T').agg({'open':'first', 'high':'max', 'low':'min', 'close':'last', 'volume':'sum'}).dropna()
 
 
 
268
  pat_scores_15m = np.full(len(df_15m), 0.5)
269
+ pat_inputs = []; valid_15m_idxs = []
 
 
 
 
270
 
271
  for i in range(200, len(df_15m)):
272
  window = df_15m.iloc[i-200:i]
273
  vec = _transform_window_for_pattern(window)
274
  if vec is not None:
275
+ pat_inputs.append(vec); valid_15m_idxs.append(i)
 
276
 
277
  if pat_inputs:
278
  X_pat = np.array(pat_inputs)
279
  pat_preds = self._smart_predict(pattern_models['15m'], xgb.DMatrix(X_pat))
280
  pat_scores_15m[valid_15m_idxs] = pat_preds
281
 
 
 
 
282
  ts_15m = df_15m.index.astype(np.int64) // 10**6
283
+ map_idxs = np.searchsorted(ts_15m, df_candidates['timestamp'].values) - 1
284
+ res_patterns = pat_scores_15m[np.clip(map_idxs, 0, len(pat_scores_15m)-1)]
 
 
 
 
285
  except Exception as e: print(f"Patterns Error: {e}")
286
 
287
+ # 3️⃣ TITAN (L2)
 
 
288
  res_titan = np.full(len(df_candidates), 0.5)
289
  if self.proc.titan and self.proc.titan.model:
290
  try:
291
  df_5m = df_1m.resample('5T').agg({'open':'first', 'high':'max', 'low':'min', 'close':'last', 'volume':'sum'}).dropna()
292
  df_5m_feat = calculate_titan_features_real(df_5m).add_prefix('5m_')
 
 
293
  ts_5m = df_5m.index.astype(np.int64) // 10**6
294
+ map_idxs = np.clip(np.searchsorted(ts_5m, df_candidates['timestamp'].values) - 1, 0, len(df_5m_feat)-1)
 
295
  feats = self.proc.titan.feature_names
296
  X_titan = df_5m_feat.iloc[map_idxs].reindex(columns=feats, fill_value=0).values
297
  res_titan = self.proc.titan.model.predict(xgb.DMatrix(X_titan, feature_names=feats))
298
  except Exception as e: print(f"Titan Error: {e}")
299
 
 
300
  # 4️⃣ SNIPER (L4)
 
301
  res_sniper = np.full(len(df_candidates), 0.5)
302
  sniper_models = getattr(self.proc.sniper, 'models', [])
303
  if sniper_models:
 
306
  for f in feats:
307
  if f not in df_candidates.columns: df_candidates[f] = 0.0
308
  X_snip = df_candidates[feats].values
309
+ # ✅ Calling internal method self._smart_predict
310
  preds = [self._extract_probs(self._smart_predict(m, X_snip)) for m in sniper_models]
311
  res_sniper = np.mean(preds, axis=0)
312
  except Exception as e: print(f"Sniper Error: {e}")
313
 
 
314
  # 5️⃣ ORACLE (L3)
 
315
  res_oracle = np.full(len(df_candidates), 0.5)
316
  oracle_model = getattr(self.proc.oracle, 'model_direction', None)
317
  if oracle_model:
318
  try:
 
319
  oracle_feats = getattr(self.proc.oracle, 'feature_cols', [])
 
 
 
 
 
 
 
 
 
320
  X_orc_df = pd.DataFrame(0.0, index=range(len(df_candidates)), columns=oracle_feats)
321
  if 'sim_titan_score' in X_orc_df: X_orc_df['sim_titan_score'] = res_titan
322
  if 'sim_pattern_score' in X_orc_df: X_orc_df['sim_pattern_score'] = res_patterns
 
325
  res_oracle = self._extract_probs(self._smart_predict(oracle_model, X_orc_df.values))
326
  except Exception as e: print(f"Oracle Error: {e}")
327
 
 
328
  # 6️⃣ HYDRA & LEGACY (L0)
 
329
  res_hydra_risk = np.zeros(len(df_candidates)); res_hydra_time = np.zeros(len(df_candidates), dtype=int)
 
330
 
331
  hydra_models = getattr(self.proc.guardian_hydra, 'models', {})
332
  if hydra_models and 'crash' in hydra_models:
 
333
  try:
334
  global_hydra_feats = np.column_stack([
335
  df_sniper['rsi_14'], df_sniper['rsi_14'], df_sniper['rsi_14'],
 
338
  ]).astype(np.float32)
339
 
340
  window_view = sliding_window_view(global_hydra_feats, 240, axis=0).transpose(0, 2, 1)
 
 
341
  c_idxs = np.searchsorted(df_sniper.index, df_candidates.index)
342
  valid_s = c_idxs + 1
343
  valid_mask_h = valid_s < (len(global_hydra_feats) - 240)
344
  final_s = valid_s[valid_mask_h]; res_idxs = np.where(valid_mask_h)[0]
345
 
 
346
  for i in range(0, len(final_s), 5000):
347
  b_idxs = final_s[i:i+5000]; r_idxs = res_idxs[i:i+5000]
348
  static = window_view[b_idxs]
 
356
  max_pnl = (np.maximum.accumulate(s_c, axis=1) - entry)/dist
357
  atr_p = s_atr/(s_c+1e-9)
358
 
 
359
  zeros = np.zeros((B, 240)); ones = np.ones((B, 240)); t = np.tile(np.arange(1, 241), (B, 1))
360
  X = np.stack([
361
  static[:,0], static[:,1], static[:,2], static[:,3], static[:,4],
 
366
  res_hydra_risk[r_idxs] = np.max(preds, axis=1)
367
  except: pass
368
 
 
369
  # 7️⃣ FINAL ASSEMBLY
 
370
  print(f" 📊 [Stats] Titan:{res_titan.mean():.2f} | Patterns:{res_patterns.mean():.2f} | Sniper:{res_sniper.mean():.2f} | Oracle:{res_oracle.mean():.2f}")
371
 
372
  ai_df = pd.DataFrame({
 
379
  'l1_score': df_candidates['l1_score'],
380
  'risk_hydra_crash': res_hydra_risk,
381
  'time_hydra_crash': res_hydra_time,
382
+ 'risk_legacy_v2': 0.0,
383
  'time_legacy_panic': 0
384
  })
385
 
 
435
  # Exit
436
  if s in pos:
437
  entry = pos[s][0]; h_r = pos[s][1]
 
 
 
438
  crash = (h_r > cfg['hydra_thresh'])
439
  pnl = (p - entry)/entry
440
 
 
465
  await self.generate_truth_data()
466
 
467
  # Grid
 
468
  oracle_r = np.linspace(0.3, 0.7, 3); sniper_r = np.linspace(0.2, 0.6, 3)
469
  hydra_r = [0.8, 0.9]; l1_r = [5.0, 10.0]
470
 
471
  combos = []
472
  for o, s, h, l1 in itertools.product(oracle_r, sniper_r, hydra_r, l1_r):
473
  combos.append({
474
+ 'w_titan': 0.4, 'w_struct': 0.3,
475
+ 'l1_thresh': l1, 'oracle_thresh': o, 'sniper_thresh': s, 'hydra_thresh': h, 'legacy_thresh': 0.95
476
  })
477
 
478
  files = glob.glob(os.path.join(CACHE_DIR, "*.pkl"))
479
  best_res = self._worker_optimize(combos, files, self.INITIAL_CAPITAL, self.TRADING_FEES, self.MAX_SLOTS)
 
480
 
481
+ # ✅ FIX: Handle empty result
482
+ if not best_res:
483
+ print("⚠️ [Warning] No trades generated in any config.")
484
+ return None, {'net_profit': 0.0, 'win_rate': 0.0}
485
+
486
  print("\n" + "="*60)
487
  print(f"🏆 CHAMPION REPORT [{target_regime}]:")
488
  print(f" 💰 Final Balance: ${best['final_balance']:,.2f}")
 
501
  print(f" ⚖️ Weights: Titan={best['config']['w_titan']:.2f} | Patterns={best['config']['w_struct']:.2f} | L1={best['config']['l1_thresh']}")
502
  print("="*60)
503
  return best['config'], best
 
504
  async def run_strategic_optimization_task():
505
  print("\n🧪 [STRATEGIC BACKTEST] Full Spectrum Mode...")
506
  r2 = R2Service(); dm = DataManager(None, None, r2); proc = MLProcessor(dm)
 
511
  opt = HeavyDutyBacktester(dm, proc)
512
 
513
  scenarios = [
514
+ {"regime": "BULL", "start": "2024-01-01", "end": "2024-03-30"},
515
+ {"regime": "BEAR", "start": "2023-08-01", "end": "2023-09-15"},
516
+ {"regime": "DEAD", "start": "2023-06-01", "end": "2023-08-01"},
517
+ {"regime": "RANGE", "start": "2024-07-01", "end": "2024-09-30"}
518
+ ]
519
 
520
  for s in scenarios:
521
  opt.set_date_range(s["start"], s["end"])