Riy777 commited on
Commit
7a8652e
Β·
verified Β·
1 Parent(s): 5b1d2b3

Update backtest_engine.py

Browse files
Files changed (1) hide show
  1. backtest_engine.py +185 -96
backtest_engine.py CHANGED
@@ -1,5 +1,5 @@
1
  # ============================================================
2
- # πŸ§ͺ backtest_engine.py (V122.1 - GEM-Architect: Method Scope Fix)
3
  # ============================================================
4
 
5
  import asyncio
@@ -34,7 +34,7 @@ logging.getLogger('ml_engine').setLevel(logging.WARNING)
34
  CACHE_DIR = "backtest_real_scores"
35
 
36
  # ============================================================
37
- # πŸ”§ 1. FEATURE ENGINEERING REPLICAS (GLOBAL SCOPE)
38
  # ============================================================
39
 
40
  def _zv(x):
@@ -146,6 +146,82 @@ def calculate_titan_features_real(df):
146
  if vwap is not None: df['VWAP_dist'] = (df['close'] / vwap) - 1
147
  return df.fillna(0)
148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  # ============================================================
150
  # πŸ§ͺ THE BACKTESTER CLASS
151
  # ============================================================
@@ -164,7 +240,7 @@ class HeavyDutyBacktester:
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):
@@ -176,23 +252,20 @@ class HeavyDutyBacktester:
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)
@@ -238,7 +311,7 @@ class HeavyDutyBacktester:
238
  print(f" πŸ“‚ [{sym}] Data Exists -> Skipping.")
239
  return
240
 
241
- print(f" βš™οΈ [CPU] Analyzing {sym} (Real Models + Patterns)...", flush=True)
242
  t0 = time.time()
243
 
244
  # 1. Base Data
@@ -248,6 +321,10 @@ class HeavyDutyBacktester:
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)
@@ -264,12 +341,14 @@ class HeavyDutyBacktester:
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)
@@ -279,7 +358,7 @@ class HeavyDutyBacktester:
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}")
@@ -288,7 +367,6 @@ class HeavyDutyBacktester:
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)
@@ -306,7 +384,6 @@ class HeavyDutyBacktester:
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}")
@@ -325,9 +402,8 @@ class HeavyDutyBacktester:
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:
@@ -366,7 +442,45 @@ class HeavyDutyBacktester:
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({
@@ -378,9 +492,8 @@ class HeavyDutyBacktester:
378
  'sniper_score': res_sniper,
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
 
386
  dt = time.time() - t0
@@ -389,7 +502,6 @@ class HeavyDutyBacktester:
389
  print(f" βœ… [{sym}] Completed {len(ai_df)} signals in {dt:.2f} seconds.", flush=True)
390
  gc.collect()
391
 
392
- # --- Standard Methods (unchanged) ---
393
  async def generate_truth_data(self):
394
  if self.force_start_date:
395
  dt_s = datetime.strptime(self.force_start_date, "%Y-%m-%d").replace(tzinfo=timezone.utc)
@@ -399,9 +511,7 @@ class HeavyDutyBacktester:
399
  for sym in self.TARGET_COINS:
400
  c = await self._fetch_all_data_fast(sym, ms_s, ms_e)
401
  if c: await self._process_data_in_memory(sym, c, ms_s, ms_e)
402
- # ============================================================
403
- # πŸ”§ FIX: Updated Worker with Consensus Metrics
404
- # ============================================================
405
  @staticmethod
406
  def _worker_optimize(combinations_batch, scores_files, initial_capital, fees_pct, max_slots):
407
  print(f" ⏳ [System] Loading {len(scores_files)} datasets...", flush=True)
@@ -412,7 +522,7 @@ class HeavyDutyBacktester:
412
  if not data: return []
413
  df = pd.concat(data).sort_values('timestamp')
414
 
415
- # Arrays extraction
416
  ts = df['timestamp'].values; close = df['close'].values.astype(float)
417
  sym = df['symbol'].values; sym_map = {s:i for i,s in enumerate(np.unique(sym))}
418
  sym_id = np.array([sym_map[s] for s in sym])
@@ -421,117 +531,94 @@ class HeavyDutyBacktester:
421
  hydra = df['risk_hydra_crash'].values; titan = df['real_titan'].values
422
  l1 = df['l1_score'].values
423
 
 
 
 
424
  N = len(ts)
425
  print(f" πŸš€ [System] Testing {len(combinations_batch)} configs on {N} candles...", flush=True)
426
 
427
  res = []
428
  for cfg in combinations_batch:
429
- pos = {}; log = [] # log stores: (pnl, was_consensus)
430
  bal = initial_capital; alloc = 0.0
431
 
432
- # Entry Logic Mask
433
  mask = (l1 >= cfg['l1_thresh']) & (oracle >= cfg['oracle_thresh']) & (sniper >= cfg['sniper_thresh'])
434
 
435
  for i in range(N):
436
  s = sym_id[i]; p = close[i]
437
 
438
- # --- EXIT LOGIC ---
439
  if s in pos:
440
- entry_price = pos[s][0]
441
- h_r = pos[s][1]
442
- entry_titan_score = pos[s][3] # Stored Titan Score
443
 
444
- crash = (h_r > cfg['hydra_thresh'])
445
- pnl = (p - entry_price) / entry_price
446
 
447
- # Simple SL/TP or Hydra Exit
448
- if crash or pnl > 0.04 or pnl < -0.02:
449
- # Fee impact (entry + exit)
450
- realized_pnl = pnl - (fees_pct * 2)
451
-
452
- bal += pos[s][2] * (1 + realized_pnl)
453
  alloc -= pos[s][2]
454
 
455
- # Check consensus for this trade (Did Titan agree?)
456
- is_consensus = (entry_titan_score > 0.55) # Threshold for agreement
457
- log.append({'pnl': realized_pnl, 'consensus': is_consensus})
458
-
459
  del pos[s]
460
 
461
- # --- ENTRY LOGIC ---
462
  if len(pos) < max_slots and mask[i]:
463
  if s not in pos and bal >= 5.0:
464
- size = min(10.0, bal * 0.98) # Fixed size per slot mostly
465
- # Store: (Price, HydraRisk, Size, TitanScore)
466
  pos[s] = (p, hydra[i], size, titan[i])
467
  bal -= size; alloc += size
468
 
469
- # --- METRICS CALCULATION ---
470
  final_bal = bal + alloc
471
- net_profit = final_bal - initial_capital
472
- total_trades = len(log)
473
  wins = sum(1 for x in log if x['pnl'] > 0)
474
- win_rate = (wins / total_trades * 100) if total_trades > 0 else 0.0
475
-
476
- # Consensus Metrics Calculation
477
- consensus_trades = [x for x in log if x['consensus']]
478
- num_con = len(consensus_trades)
479
-
480
- agreement_rate = (num_con / total_trades * 100) if total_trades > 0 else 0.0
481
- con_wins = sum(1 for x in consensus_trades if x['pnl'] > 0)
482
- con_win_rate = (con_wins / num_con * 100) if num_con > 0 else 0.0
483
- con_avg_pnl = (sum(x['pnl'] for x in consensus_trades) / num_con * 100) if num_con > 0 else 0.0
484
 
 
 
 
 
 
 
 
485
  res.append({
486
- 'config': cfg,
487
- 'final_balance': final_bal, # βœ… Added
488
- 'net_profit': net_profit,
489
- 'total_trades': total_trades,
490
- 'win_rate': win_rate,
491
- 'max_drawdown': 0.0, # Simplified for speed
492
- 'consensus_agreement_rate': agreement_rate, # βœ… Added
493
- 'high_consensus_win_rate': con_win_rate, # βœ… Added
494
- 'high_consensus_avg_pnl': con_avg_pnl # βœ… Added
495
  })
496
-
497
  return res
498
 
499
- # ============================================================
500
- # πŸ”§ FIX: Run Optimization with Safe Sorting
501
- # ============================================================
502
  async def run_optimization(self, target_regime="RANGE"):
503
  await self.generate_truth_data()
504
 
505
- # Grid Setup
506
- oracle_r = np.linspace(0.5, 0.8, 3)
507
- sniper_r = np.linspace(0.3, 0.6, 3)
508
- hydra_r = [0.85, 0.95]
509
- l1_r = [5.0]
510
 
511
  combos = []
512
  for o, s, h, l1 in itertools.product(oracle_r, sniper_r, hydra_r, l1_r):
513
  combos.append({
514
  'w_titan': 0.4, 'w_struct': 0.3,
515
- 'l1_thresh': l1, 'oracle_thresh': o, 'sniper_thresh': s,
516
- 'hydra_thresh': h, 'legacy_thresh': 0.95
517
  })
518
 
519
  files = glob.glob(os.path.join(CACHE_DIR, "*.pkl"))
520
- if not files:
521
- print("⚠️ [Warning] No data files found for backtest.")
522
- return None, {'net_profit': 0.0}
523
-
524
- # Run Worker
525
  results_list = self._worker_optimize(combos, files, self.INITIAL_CAPITAL, self.TRADING_FEES, self.MAX_SLOTS)
526
 
 
527
  if not results_list:
528
  print("⚠️ [Warning] No trades generated in any config.")
529
  return None, {'net_profit': 0.0, 'win_rate': 0.0}
530
 
531
- # βœ… FIX: Sort and select 'best'
532
- # Sort by Net Profit descending
533
  results_list.sort(key=lambda x: x['net_profit'], reverse=True)
534
- best = results_list[0] # The Champion
535
 
536
  print("\n" + "="*60)
537
  print(f"πŸ† CHAMPION REPORT [{target_regime}]:")
@@ -540,23 +627,23 @@ class HeavyDutyBacktester:
540
  print("-" * 60)
541
  print(f" πŸ“Š Total Trades: {best['total_trades']}")
542
  print(f" πŸ“ˆ Win Rate: {best['win_rate']:.1f}%")
543
- print(f" πŸ“‰ Max Drawdown: {best['max_drawdown']:.1f}% (Approx)")
544
  print("-" * 60)
545
  print(f" 🧠 CONSENSUS ANALYTICS:")
546
- print(f" 🀝 Model Agreement Rate: {best['consensus_agreement_rate']:.1f}% (Titan Agreed)")
547
  print(f" 🌟 High-Consensus Win Rate: {best['high_consensus_win_rate']:.1f}%")
548
  print(f" πŸ’Ž High-Consensus Avg PnL: {best['high_consensus_avg_pnl']:.2f}%")
549
  print("-" * 60)
550
  print(f" βš™οΈ Oracle={best['config']['oracle_thresh']:.2f} | Sniper={best['config']['sniper_thresh']:.2f} | Hydra={best['config']['hydra_thresh']:.2f}")
 
551
  print("="*60)
552
-
553
  return best['config'], best
554
-
555
  async def run_strategic_optimization_task():
556
  print("\nπŸ§ͺ [STRATEGIC BACKTEST] Full Spectrum Mode...")
557
  r2 = R2Service(); dm = DataManager(None, None, r2); proc = MLProcessor(dm)
558
  await dm.initialize(); await proc.initialize()
559
- proc.guardian_hydra.set_silent_mode(True)
560
 
561
  hub = AdaptiveHub(r2); await hub.initialize()
562
  opt = HeavyDutyBacktester(dm, proc)
@@ -572,7 +659,9 @@ async def run_strategic_optimization_task():
572
  opt.set_date_range(s["start"], s["end"])
573
  best_cfg, best_stats = await opt.run_optimization(s["regime"])
574
  if best_cfg: hub.submit_challenger(s["regime"], best_cfg, best_stats)
575
-
 
 
576
  await hub._save_state_to_r2()
577
  print("βœ… [System] DNA Updated.")
578
 
 
1
  # ============================================================
2
+ # πŸ§ͺ backtest_engine.py (V123.0 - GEM-Architect: Real Legacy & Consensus Fix)
3
  # ============================================================
4
 
5
  import asyncio
 
34
  CACHE_DIR = "backtest_real_scores"
35
 
36
  # ============================================================
37
+ # πŸ”§ 1. FEATURE ENGINEERING (GLOBAL & VECTORIZED)
38
  # ============================================================
39
 
40
  def _zv(x):
 
146
  if vwap is not None: df['VWAP_dist'] = (df['close'] / vwap) - 1
147
  return df.fillna(0)
148
 
149
+ # ============================================================
150
+ # πŸ”§ NEW: LEGACY GUARD VECTORIZED FEATURES (REAL EXECUTION)
151
+ # ============================================================
152
+ def calculate_legacy_v2_vectorized(df_1m, df_5m, df_15m):
153
+ """
154
+ Replicates HybridDeepSteward V2 features in a vectorized way for speed.
155
+ """
156
+ try:
157
+ def calc_basic(df, suffix):
158
+ c = df['close']; h = df['high']; l = df['low']
159
+ res = pd.DataFrame(index=df.index)
160
+ res[f'log_ret_{suffix}'] = np.log(c / c.shift(1)).fillna(0)
161
+ res[f'rsi_{suffix}'] = (ta.rsi(c, 14) / 100.0).fillna(0.5)
162
+
163
+ roll_max = h.rolling(50).max(); roll_min = l.rolling(50).min()
164
+ diff = (roll_max - roll_min).replace(0, 1e-9)
165
+ res[f'fib_pos_{suffix}'] = ((c - roll_min) / diff).fillna(0.5)
166
+
167
+ if suffix == '1m':
168
+ res[f'volatility_{suffix}'] = (ta.atr(h, l, c, 14) / c).fillna(0)
169
+ else:
170
+ ema = ta.ema(c, 20)
171
+ res[f'trend_slope_{suffix}'] = ((ema - ema.shift(5)) / ema.shift(5)).fillna(0)
172
+ if suffix == '15m':
173
+ fib618 = roll_max - (diff * 0.382)
174
+ res[f'dist_fib618_{suffix}'] = ((c - fib618) / c).fillna(0)
175
+ return res.fillna(0)
176
+
177
+ f1 = calc_basic(df_1m, '1m')
178
+ f5 = calc_basic(df_5m, '5m').reindex(df_1m.index, method='ffill')
179
+ f15 = calc_basic(df_15m, '15m').reindex(df_1m.index, method='ffill')
180
+
181
+ FEATS_1M = ['log_ret_1m', 'rsi_1m', 'fib_pos_1m', 'volatility_1m']
182
+ FEATS_5M = ['log_ret_5m', 'rsi_5m', 'fib_pos_5m', 'trend_slope_5m']
183
+ FEATS_15M = ['log_ret_15m', 'rsi_15m', 'dist_fib618_15m', 'trend_slope_15m']
184
+
185
+ parts = [f1[FEATS_1M], f5[FEATS_5M], f15[FEATS_15M]]
186
+
187
+ lags = [1, 2, 3, 5, 10, 20]
188
+ for lag in lags:
189
+ lagged = f1[FEATS_1M].shift(lag).fillna(0)
190
+ parts.append(lagged)
191
+
192
+ X_df = pd.concat(parts, axis=1)
193
+ return X_df.values
194
+ except Exception as e:
195
+ print(f"Legacy V2 Vec Error: {e}")
196
+ return None
197
+
198
+ def calculate_legacy_v3_vectorized(df_1m, df_5m, df_15m, feature_names):
199
+ """Replicates HybridDeepSteward V3 features"""
200
+ try:
201
+ def calc_v3(df, suffix=""):
202
+ d = df.copy()
203
+ d['rsi'] = ta.rsi(d['close'], 14).fillna(50)
204
+ d['ema_50'] = ta.ema(d['close'], 50).fillna(d['close'])
205
+ d['ema_200'] = ta.ema(d['close'], 200).fillna(d['close'])
206
+ d['dist_ema50'] = (d['close'] - d['ema_50'])/d['close']
207
+ d['dist_ema200'] = (d['close'] - d['ema_200'])/d['close']
208
+ d['log_ret'] = np.log(d['close']/d['close'].shift(1)).fillna(0)
209
+
210
+ cols = ['rsi', 'dist_ema50', 'dist_ema200', 'log_ret']
211
+ res = d[cols].copy()
212
+ if suffix: res.columns = [f"{c}_{suffix}" for c in cols]
213
+ return res.fillna(0)
214
+
215
+ v1 = calc_v3(df_1m)
216
+ v5 = calc_v3(df_5m, "5m").reindex(df_1m.index, method='ffill')
217
+ v15 = calc_v3(df_15m, "15m").reindex(df_1m.index, method='ffill')
218
+
219
+ full_df = pd.concat([v1, v5, v15], axis=1)
220
+
221
+ if not feature_names: return None
222
+ return full_df.reindex(columns=feature_names, fill_value=0).values
223
+ except: return None
224
+
225
  # ============================================================
226
  # πŸ§ͺ THE BACKTESTER CLASS
227
  # ============================================================
 
240
  for f in glob.glob(os.path.join(CACHE_DIR, "*")): os.remove(f)
241
  else: os.makedirs(CACHE_DIR)
242
 
243
+ print(f"πŸ§ͺ [Backtest V123.0] Real Legacy & Consensus. Checking Engines...")
244
  self._check_engines()
245
 
246
  def _check_engines(self):
 
252
  if self.proc.oracle: status.append("Oracle(Real)")
253
  if self.proc.sniper: status.append("Sniper(Real)")
254
  if self.proc.guardian_hydra: status.append("Hydra(Real)")
255
+ if self.proc.guardian_legacy: status.append("Legacy(Real)")
256
  print(f" βœ… Engines Ready: {', '.join(status)}")
257
 
258
  def set_date_range(self, start_str, end_str):
259
  self.force_start_date = start_str; self.force_end_date = end_str
260
 
 
261
  def _smart_predict(self, model, X):
262
  """Forces predict_proba and handles shape mismatch"""
263
  try:
 
264
  if hasattr(model, "predict_proba"):
265
  raw = model.predict_proba(X)
266
  if raw.ndim == 2:
267
+ return raw[:, -1]
268
  return raw
 
 
269
  return model.predict(X)
270
  except:
271
  return np.zeros(len(X) if hasattr(X, '__len__') else 0)
 
311
  print(f" πŸ“‚ [{sym}] Data Exists -> Skipping.")
312
  return
313
 
314
+ print(f" βš™οΈ [CPU] Analyzing {sym} (ALL REAL MODELS)...", flush=True)
315
  t0 = time.time()
316
 
317
  # 1. Base Data
 
321
  df_1m.set_index('datetime', inplace=True)
322
  df_1m = df_1m.sort_index()
323
 
324
+ # Aux Dataframes for Legacy
325
+ df_5m = df_1m.resample('5T').agg({'open':'first', 'high':'max', 'low':'min', 'close':'last', 'volume':'sum'}).dropna()
326
+ df_15m = df_1m.resample('15T').agg({'open':'first', 'high':'max', 'low':'min', 'close':'last', 'volume':'sum'}).dropna()
327
+
328
  # 1️⃣ SNIPER & L1 SCORING (L4/L1)
329
  df_sniper = calculate_sniper_features_exact(df_1m)
330
  df_sniper['rel_vol'] = df_sniper['volume'] / (df_sniper['volume'].rolling(50).mean() + 1e-9)
 
341
  pattern_models = getattr(self.proc.pattern_engine, 'models', {})
342
  if pattern_models and '15m' in pattern_models:
343
  try:
344
+ # ⚠️ Note: We rely on the internal feature calculation here for speed.
345
+ # In real execution, Pattern Engine handles this.
346
+ df_15m_res = df_1m.resample('15T').agg({'open':'first', 'high':'max', 'low':'min', 'close':'last', 'volume':'sum'}).dropna()
347
+ pat_scores_15m = np.full(len(df_15m_res), 0.5)
348
  pat_inputs = []; valid_15m_idxs = []
349
 
350
+ for i in range(200, len(df_15m_res)):
351
+ window = df_15m_res.iloc[i-200:i]
352
  vec = _transform_window_for_pattern(window)
353
  if vec is not None:
354
  pat_inputs.append(vec); valid_15m_idxs.append(i)
 
358
  pat_preds = self._smart_predict(pattern_models['15m'], xgb.DMatrix(X_pat))
359
  pat_scores_15m[valid_15m_idxs] = pat_preds
360
 
361
+ ts_15m = df_15m_res.index.astype(np.int64) // 10**6
362
  map_idxs = np.searchsorted(ts_15m, df_candidates['timestamp'].values) - 1
363
  res_patterns = pat_scores_15m[np.clip(map_idxs, 0, len(pat_scores_15m)-1)]
364
  except Exception as e: print(f"Patterns Error: {e}")
 
367
  res_titan = np.full(len(df_candidates), 0.5)
368
  if self.proc.titan and self.proc.titan.model:
369
  try:
 
370
  df_5m_feat = calculate_titan_features_real(df_5m).add_prefix('5m_')
371
  ts_5m = df_5m.index.astype(np.int64) // 10**6
372
  map_idxs = np.clip(np.searchsorted(ts_5m, df_candidates['timestamp'].values) - 1, 0, len(df_5m_feat)-1)
 
384
  for f in feats:
385
  if f not in df_candidates.columns: df_candidates[f] = 0.0
386
  X_snip = df_candidates[feats].values
 
387
  preds = [self._extract_probs(self._smart_predict(m, X_snip)) for m in sniper_models]
388
  res_sniper = np.mean(preds, axis=0)
389
  except Exception as e: print(f"Sniper Error: {e}")
 
402
  res_oracle = self._extract_probs(self._smart_predict(oracle_model, X_orc_df.values))
403
  except Exception as e: print(f"Oracle Error: {e}")
404
 
405
+ # 6️⃣ HYDRA (L0)
406
+ res_hydra_risk = np.zeros(len(df_candidates))
 
407
  hydra_models = getattr(self.proc.guardian_hydra, 'models', {})
408
  if hydra_models and 'crash' in hydra_models:
409
  try:
 
442
  res_hydra_risk[r_idxs] = np.max(preds, axis=1)
443
  except: pass
444
 
445
+ # 7️⃣ REAL LEGACY GUARD (V2 & V3) - πŸ›‘οΈ NEW
446
+ res_legacy_v2 = np.zeros(len(df_candidates))
447
+ res_legacy_v3 = np.zeros(len(df_candidates))
448
+
449
+ if self.proc.guardian_legacy:
450
+ try:
451
+ # Prepare Vectorized Features for the entire timeline for correctness
452
+ X_v2_full = calculate_legacy_v2_vectorized(df_1m, df_5m, df_15m)
453
+ v3_feats = self.proc.guardian_legacy.v3_feature_names
454
+ X_v3_full = calculate_legacy_v3_vectorized(df_1m, df_5m, df_15m, v3_feats)
455
+
456
+ # Map candidates to full df indices
457
+ # valid_mask is boolean array same length as df_1m (approx)
458
+ # We need exact indices in X_v2_full matching df_candidates
459
+ all_indices = np.arange(len(df_1m))
460
+ cand_indices = all_indices[valid_mask]
461
+
462
+ # Safety Clip
463
+ max_len = len(X_v2_full)
464
+ cand_indices = cand_indices[cand_indices < max_len]
465
+
466
+ if len(cand_indices) > 0:
467
+ # Predict V2
468
+ if self.proc.guardian_legacy.model_v2 and X_v2_full is not None:
469
+ subset_v2 = X_v2_full[cand_indices]
470
+ preds_v2 = self.proc.guardian_legacy.model_v2.predict(xgb.DMatrix(subset_v2))
471
+ if len(preds_v2.shape) > 1: res_legacy_v2[:len(cand_indices)] = preds_v2[:, 2] # Panic Class
472
+ else: res_legacy_v2[:len(cand_indices)] = preds_v2
473
+
474
+ # Predict V3
475
+ if self.proc.guardian_legacy.model_v3 and X_v3_full is not None:
476
+ subset_v3 = X_v3_full[cand_indices]
477
+ preds_v3 = self.proc.guardian_legacy.model_v3.predict(xgb.DMatrix(subset_v3))
478
+ res_legacy_v3[:len(cand_indices)] = preds_v3
479
+
480
+ except Exception as e:
481
+ print(f"❌ Legacy Guard Error: {e}")
482
+
483
+ # 8️⃣ FINAL ASSEMBLY
484
  print(f" πŸ“Š [Stats] Titan:{res_titan.mean():.2f} | Patterns:{res_patterns.mean():.2f} | Sniper:{res_sniper.mean():.2f} | Oracle:{res_oracle.mean():.2f}")
485
 
486
  ai_df = pd.DataFrame({
 
492
  'sniper_score': res_sniper,
493
  'l1_score': df_candidates['l1_score'],
494
  'risk_hydra_crash': res_hydra_risk,
495
+ 'risk_legacy_v2': res_legacy_v2, # βœ… REAL
496
+ 'risk_legacy_v3': res_legacy_v3 # βœ… REAL
 
497
  })
498
 
499
  dt = time.time() - t0
 
502
  print(f" βœ… [{sym}] Completed {len(ai_df)} signals in {dt:.2f} seconds.", flush=True)
503
  gc.collect()
504
 
 
505
  async def generate_truth_data(self):
506
  if self.force_start_date:
507
  dt_s = datetime.strptime(self.force_start_date, "%Y-%m-%d").replace(tzinfo=timezone.utc)
 
511
  for sym in self.TARGET_COINS:
512
  c = await self._fetch_all_data_fast(sym, ms_s, ms_e)
513
  if c: await self._process_data_in_memory(sym, c, ms_s, ms_e)
514
+
 
 
515
  @staticmethod
516
  def _worker_optimize(combinations_batch, scores_files, initial_capital, fees_pct, max_slots):
517
  print(f" ⏳ [System] Loading {len(scores_files)} datasets...", flush=True)
 
522
  if not data: return []
523
  df = pd.concat(data).sort_values('timestamp')
524
 
525
+ # Arrays
526
  ts = df['timestamp'].values; close = df['close'].values.astype(float)
527
  sym = df['symbol'].values; sym_map = {s:i for i,s in enumerate(np.unique(sym))}
528
  sym_id = np.array([sym_map[s] for s in sym])
 
531
  hydra = df['risk_hydra_crash'].values; titan = df['real_titan'].values
532
  l1 = df['l1_score'].values
533
 
534
+ legacy_v2 = df['risk_legacy_v2'].values # βœ… REAL
535
+ legacy_v3 = df['risk_legacy_v3'].values # βœ… REAL
536
+
537
  N = len(ts)
538
  print(f" πŸš€ [System] Testing {len(combinations_batch)} configs on {N} candles...", flush=True)
539
 
540
  res = []
541
  for cfg in combinations_batch:
542
+ pos = {}; log = []
543
  bal = initial_capital; alloc = 0.0
544
 
 
545
  mask = (l1 >= cfg['l1_thresh']) & (oracle >= cfg['oracle_thresh']) & (sniper >= cfg['sniper_thresh'])
546
 
547
  for i in range(N):
548
  s = sym_id[i]; p = close[i]
549
 
550
+ # Exit
551
  if s in pos:
552
+ entry = pos[s][0]; h_r = pos[s][1]; titan_entry = pos[s][3]
 
 
553
 
554
+ crash_hydra = (h_r > cfg['hydra_thresh'])
555
+ panic_legacy = (legacy_v2[i] > cfg['legacy_thresh']) or (legacy_v3[i] > cfg['legacy_thresh'])
556
 
557
+ pnl = (p - entry)/entry
558
+
559
+ if crash_hydra or panic_legacy or pnl > 0.04 or pnl < -0.02:
560
+ realized = pnl - fees_pct*2
561
+ bal += pos[s][2] * (1 + realized)
 
562
  alloc -= pos[s][2]
563
 
564
+ is_consensus = (titan_entry > 0.55)
565
+ log.append({'pnl': realized, 'consensus': is_consensus})
 
 
566
  del pos[s]
567
 
568
+ # Entry
569
  if len(pos) < max_slots and mask[i]:
570
  if s not in pos and bal >= 5.0:
571
+ size = min(10.0, bal * 0.98)
 
572
  pos[s] = (p, hydra[i], size, titan[i])
573
  bal -= size; alloc += size
574
 
575
+ # Metrics
576
  final_bal = bal + alloc
577
+ profit = final_bal - initial_capital
578
+ tot = len(log)
579
  wins = sum(1 for x in log if x['pnl'] > 0)
580
+ win_rate = (wins/tot*100) if tot else 0
 
 
 
 
 
 
 
 
 
581
 
582
+ # Consensus Metrics
583
+ cons_trades = [x for x in log if x['consensus']]
584
+ n_cons = len(cons_trades)
585
+ agree_rate = (n_cons/tot*100) if tot else 0
586
+ cons_win_rate = (sum(1 for x in cons_trades if x['pnl']>0)/n_cons*100) if n_cons else 0
587
+ cons_avg_pnl = (sum(x['pnl'] for x in cons_trades)/n_cons*100) if n_cons else 0
588
+
589
  res.append({
590
+ 'config': cfg, 'final_balance': final_bal, 'net_profit': profit,
591
+ 'total_trades': tot, 'win_rate': win_rate, 'max_drawdown': 0,
592
+ 'consensus_agreement_rate': agree_rate,
593
+ 'high_consensus_win_rate': cons_win_rate,
594
+ 'high_consensus_avg_pnl': cons_avg_pnl
 
 
 
 
595
  })
 
596
  return res
597
 
 
 
 
598
  async def run_optimization(self, target_regime="RANGE"):
599
  await self.generate_truth_data()
600
 
601
+ # Grid
602
+ oracle_r = np.linspace(0.3, 0.7, 3); sniper_r = np.linspace(0.2, 0.6, 3)
603
+ hydra_r = [0.8, 0.9]; l1_r = [5.0, 10.0]
 
 
604
 
605
  combos = []
606
  for o, s, h, l1 in itertools.product(oracle_r, sniper_r, hydra_r, l1_r):
607
  combos.append({
608
  'w_titan': 0.4, 'w_struct': 0.3,
609
+ 'l1_thresh': l1, 'oracle_thresh': o, 'sniper_thresh': s, 'hydra_thresh': h, 'legacy_thresh': 0.95
 
610
  })
611
 
612
  files = glob.glob(os.path.join(CACHE_DIR, "*.pkl"))
 
 
 
 
 
613
  results_list = self._worker_optimize(combos, files, self.INITIAL_CAPITAL, self.TRADING_FEES, self.MAX_SLOTS)
614
 
615
+ # βœ… FIX: Handle empty result & Sort for 'best'
616
  if not results_list:
617
  print("⚠️ [Warning] No trades generated in any config.")
618
  return None, {'net_profit': 0.0, 'win_rate': 0.0}
619
 
 
 
620
  results_list.sort(key=lambda x: x['net_profit'], reverse=True)
621
+ best = results_list[0]
622
 
623
  print("\n" + "="*60)
624
  print(f"πŸ† CHAMPION REPORT [{target_regime}]:")
 
627
  print("-" * 60)
628
  print(f" πŸ“Š Total Trades: {best['total_trades']}")
629
  print(f" πŸ“ˆ Win Rate: {best['win_rate']:.1f}%")
630
+ print(f" πŸ“‰ Max Drawdown: {best['max_drawdown']:.1f}%")
631
  print("-" * 60)
632
  print(f" 🧠 CONSENSUS ANALYTICS:")
633
+ print(f" 🀝 Model Agreement Rate: {best['consensus_agreement_rate']:.1f}%")
634
  print(f" 🌟 High-Consensus Win Rate: {best['high_consensus_win_rate']:.1f}%")
635
  print(f" πŸ’Ž High-Consensus Avg PnL: {best['high_consensus_avg_pnl']:.2f}%")
636
  print("-" * 60)
637
  print(f" βš™οΈ Oracle={best['config']['oracle_thresh']:.2f} | Sniper={best['config']['sniper_thresh']:.2f} | Hydra={best['config']['hydra_thresh']:.2f}")
638
+ print(f" βš–οΈ Weights: Titan={best['config']['w_titan']:.2f} | Patterns={best['config']['w_struct']:.2f} | L1={best['config']['l1_thresh']}")
639
  print("="*60)
 
640
  return best['config'], best
641
+
642
  async def run_strategic_optimization_task():
643
  print("\nπŸ§ͺ [STRATEGIC BACKTEST] Full Spectrum Mode...")
644
  r2 = R2Service(); dm = DataManager(None, None, r2); proc = MLProcessor(dm)
645
  await dm.initialize(); await proc.initialize()
646
+ if proc.guardian_hydra: proc.guardian_hydra.set_silent_mode(True)
647
 
648
  hub = AdaptiveHub(r2); await hub.initialize()
649
  opt = HeavyDutyBacktester(dm, proc)
 
659
  opt.set_date_range(s["start"], s["end"])
660
  best_cfg, best_stats = await opt.run_optimization(s["regime"])
661
  if best_cfg: hub.submit_challenger(s["regime"], best_cfg, best_stats)
662
+
663
+ # Clean up
664
+ await dm.close()
665
  await hub._save_state_to_r2()
666
  print("βœ… [System] DNA Updated.")
667