Riy777 commited on
Commit
3192b4e
·
verified ·
1 Parent(s): 3060cdb

Update backtest_engine.py

Browse files
Files changed (1) hide show
  1. backtest_engine.py +64 -83
backtest_engine.py CHANGED
@@ -1,5 +1,5 @@
1
  # ============================================================
2
- # 🧪 backtest_engine.py (V140.0 - GEM-Architect: Bulletproof Logic)
3
  # ============================================================
4
 
5
  import asyncio
@@ -47,7 +47,7 @@ def _z_roll(x, w=500):
47
  return ((x - r) / s).fillna(0)
48
 
49
  def _revive_score_distribution(scores):
50
- scores = np.array(scores, dtype=np.float32)
51
  if len(scores) < 10: return scores
52
  std = np.std(scores)
53
  if std < 0.05:
@@ -73,6 +73,9 @@ def _zv(x):
73
  s = np.nanstd(x, axis=0) + 1e-9
74
  return np.nan_to_num((x - m) / s, nan=0.0)
75
 
 
 
 
76
  def _transform_window_for_pattern(df_window):
77
  try:
78
  c = df_window['close'].values.astype('float32')
@@ -80,12 +83,14 @@ def _transform_window_for_pattern(df_window):
80
  h = df_window['high'].values.astype('float32')
81
  l = df_window['low'].values.astype('float32')
82
  v = df_window['volume'].values.astype('float32')
 
83
  base = np.stack([o, h, l, c, v], axis=1)
84
  base_z = _zv(base)
85
  lr = np.zeros_like(c); lr[1:] = np.diff(np.log1p(c))
86
  rng = (h - l) / (c + 1e-9)
87
  extra = np.stack([lr, rng], axis=1)
88
  extra_z = _zv(extra)
 
89
  def _ema(arr, n): return pd.Series(arr).ewm(span=n, adjust=False).mean().values
90
  ema9 = _ema(c, 9); ema21 = _ema(c, 21); ema50 = _ema(c, 50); ema200 = _ema(c, 200)
91
  slope21 = np.gradient(ema21); slope50 = np.gradient(ema50)
@@ -96,6 +101,7 @@ def _transform_window_for_pattern(df_window):
96
  roll_down = pd.Series(down).abs().ewm(alpha=1/14, adjust=False).mean().values
97
  rs = roll_up / (roll_down + 1e-9)
98
  rsi = 100.0 - (100.0 / (1.0 + rs))
 
99
  indicators = np.stack([ema9, ema21, ema50, ema200, slope21, slope50, rsi], axis=1)
100
  X_seq = np.concatenate([base_z, extra_z, _zv(indicators)], axis=1)
101
  X_flat = X_seq.flatten()
@@ -114,13 +120,15 @@ class HeavyDutyBacktester:
114
  self.INITIAL_CAPITAL = 10.0
115
  self.TRADING_FEES = 0.001
116
  self.MAX_SLOTS = 4
 
117
  self.TARGET_COINS = [
118
- 'SOL/USDT'
119
- ]
120
  self.force_start_date = None
121
  self.force_end_date = None
 
122
  if not os.path.exists(CACHE_DIR): os.makedirs(CACHE_DIR)
123
- print(f"🧪 [Backtest V140.0] Bulletproof Scalar Logic.")
124
 
125
  def set_date_range(self, start_str, end_str):
126
  self.force_start_date = start_str
@@ -137,6 +145,7 @@ class HeavyDutyBacktester:
137
  current += duration_per_batch
138
  all_candles = []
139
  sem = asyncio.Semaphore(20)
 
140
  async def _fetch_batch(timestamp):
141
  async with sem:
142
  for _ in range(3):
@@ -144,6 +153,7 @@ class HeavyDutyBacktester:
144
  return await self.dm.exchange.fetch_ohlcv(sym, '1m', since=timestamp, limit=limit)
145
  except: await asyncio.sleep(0.5)
146
  return []
 
147
  chunk_size = 50
148
  for i in range(0, len(tasks), chunk_size):
149
  chunk_tasks = tasks[i:i + chunk_size]
@@ -151,6 +161,7 @@ class HeavyDutyBacktester:
151
  results = await asyncio.gather(*futures)
152
  for res in results:
153
  if res: all_candles.extend(res)
 
154
  if not all_candles: return None
155
  df = pd.DataFrame(all_candles, columns=['timestamp', 'o', 'h', 'l', 'c', 'v'])
156
  df.drop_duplicates('timestamp', inplace=True)
@@ -159,10 +170,15 @@ class HeavyDutyBacktester:
159
  print(f" ✅ Downloaded {len(df)} candles.", flush=True)
160
  return df.values.tolist()
161
 
 
 
 
162
  def _calculate_indicators_vectorized(self, df, timeframe='1m'):
163
  cols = ['close', 'high', 'low', 'volume', 'open']
164
  for c in cols: df[c] = df[c].astype(np.float64)
165
  idx = df.index
 
 
166
  df['RSI'] = safe_ta(ta.rsi(df['close'], length=14), idx, 50)
167
  macd = ta.macd(df['close'])
168
  if macd is not None:
@@ -173,28 +189,35 @@ class HeavyDutyBacktester:
173
  adx = ta.adx(df['high'], df['low'], df['close'], length=14)
174
  if adx is not None: df['ADX'] = safe_ta(adx.iloc[:, 0], idx, 0)
175
  else: df['ADX'] = 0.0
176
- if timeframe == '1d': df['Trend_Strong'] = np.where(df['ADX'] > 25, 1.0, 0.0)
 
 
177
  for p in [9, 21, 50, 200]:
178
  ema = safe_ta(ta.ema(df['close'], length=p), idx, 0)
179
  df[f'EMA_{p}_dist'] = ((df['close'] / ema.replace(0, np.nan)) - 1).fillna(0)
180
  df[f'ema{p}'] = ema
 
181
  df['ema20'] = safe_ta(ta.ema(df['close'], length=20), idx, df['close'])
 
182
  bb = ta.bbands(df['close'], length=20, std=2.0)
183
  if bb is not None:
184
  w = ((bb.iloc[:, 2] - bb.iloc[:, 0]) / bb.iloc[:, 1].replace(0, np.nan)).fillna(0)
185
  p = ((df['close'] - bb.iloc[:, 0]) / (bb.iloc[:, 2] - bb.iloc[:, 0]).replace(0, np.nan)).fillna(0)
186
  df['BB_w'] = w; df['BB_p'] = p; df['bb_width'] = w
187
  else: df['BB_w'] = 0; df['BB_p'] = 0; df['bb_width'] = 0
 
188
  df['MFI'] = safe_ta(ta.mfi(df['high'], df['low'], df['close'], df['volume'], length=14), idx, 50)
189
  vwap = ta.vwap(df['high'], df['low'], df['close'], df['volume'])
190
  if vwap is not None:
191
  df['VWAP_dist'] = ((df['close'] / vwap.replace(0, np.nan)) - 1).fillna(0)
192
  df['vwap'] = vwap
193
  else: df['VWAP_dist'] = 0.0; df['vwap'] = df['close']
 
194
  df['atr'] = safe_ta(ta.atr(df['high'], df['low'], df['close'], length=14), idx, 0)
195
  df['atr_pct'] = (df['atr'] / df['close'].replace(0, np.nan)).fillna(0)
196
  df['ATR_pct'] = df['atr_pct']
197
 
 
198
  if timeframe == '1m':
199
  df['return_1m'] = df['close'].pct_change().fillna(0)
200
  df['return_3m'] = df['close'].pct_change(3).fillna(0)
@@ -233,14 +256,14 @@ class HeavyDutyBacktester:
233
  df['rv_gk'] = _z_roll(rv_gk)
234
  df['L_score'] = (df['vol_zscore_50'] - df['amihud'] - df['roll_spread'] - df['rv_gk'].abs() - df['vwap_dev'].abs() + df['ofi']).fillna(0)
235
 
 
236
  df['slope'] = safe_ta(ta.slope(df['close'], length=7), idx, 0)
237
  vol_mean = df['volume'].rolling(20).mean()
238
  vol_std = df['volume'].rolling(20).std().replace(0, np.nan)
239
  df['vol_z'] = ((df['volume'] - vol_mean) / vol_std).fillna(0)
240
  df['rel_vol'] = df['volume'] / (df['volume'].rolling(50).mean() + 1e-9)
241
  df['log_ret'] = np.log(df['close'] / df['close'].shift(1).replace(0, np.nan)).fillna(0)
242
- roll_max = df['high'].rolling(50).max()
243
- roll_min = df['low'].rolling(50).min()
244
  diff = (roll_max - roll_min).replace(0, 1e-9)
245
  df['fib_pos'] = ((df['close'] - roll_min) / diff).fillna(0.5)
246
  e20_s = df['ema20'].shift(5).replace(0, np.nan)
@@ -252,19 +275,25 @@ class HeavyDutyBacktester:
252
  e200 = safe_ta(ta.ema(df['close'], length=200), idx, df['close'])
253
  df['ema200'] = e200
254
  df['dist_ema200'] = ((df['close'] - e200) / e200.replace(0, np.nan)).fillna(0)
 
255
  if timeframe == '1m':
256
  for lag in [1, 2, 3, 5, 10, 20]:
257
  df[f'log_ret_lag_{lag}'] = df['log_ret'].shift(lag).fillna(0)
258
  df[f'rsi_lag_{lag}'] = (df['RSI'].shift(lag) / 100.0).fillna(0.5)
259
  df[f'fib_pos_lag_{lag}'] = df['fib_pos'].shift(lag).fillna(0.5)
260
  df[f'volatility_lag_{lag}'] = df['volatility'].shift(lag).fillna(0)
 
261
  df.fillna(0, inplace=True)
262
  return df
263
 
 
 
 
264
  async def _process_data_in_memory(self, sym, candles, start_ms, end_ms):
265
  safe_sym = sym.replace('/', '_')
266
  period_suffix = f"{start_ms}_{end_ms}"
267
  scores_file = f"{CACHE_DIR}/{safe_sym}_{period_suffix}_scores.pkl"
 
268
  if os.path.exists(scores_file):
269
  print(f" 📂 [{sym}] Data Exists -> Skipping.")
270
  return
@@ -332,7 +361,8 @@ class HeavyDutyBacktester:
332
  elif target_arr and feat in ['open','high','low','close','volume']: t_vecs.append(target_arr[feat][target_map])
333
  else: t_vecs.append(np.zeros(len(arr_ts_1m)))
334
  X_TITAN = np.column_stack(t_vecs)
335
- global_titan_scores = _revive_score_distribution(titan_model.predict(xgb.DMatrix(X_TITAN, feature_names=titan_cols)))
 
336
  except: pass
337
 
338
  # B. SNIPER (Global)
@@ -383,6 +413,7 @@ class HeavyDutyBacktester:
383
  X_V2 = np.column_stack([l_log, l_rsi, l_fib, l_vol, l5_log, l5_rsi, l5_fib, l5_trd, l15_log, l15_rsi, l15_fib618, l15_trd, *lags])
384
  preds = legacy_v2.predict(xgb.DMatrix(X_V2))
385
  global_v2_scores = preds[:, 2] if len(preds.shape) > 1 else preds
 
386
  except: pass
387
 
388
  # Filter
@@ -407,13 +438,15 @@ class HeavyDutyBacktester:
407
  for idx in chunk_idxs:
408
  sl_st = h_static[idx:idx+240]
409
  sl_close = sl_st[:, 6]; sl_atr = sl_st[:, 5]
410
- entry = fast_1m['close'][idx]
411
  dist = np.maximum(1.5 * sl_atr, entry * 0.015)
412
  pnl = sl_close - entry
413
  norm_pnl = pnl / dist
414
  max_pnl_r = (np.maximum.accumulate(sl_close) - entry) / dist
415
  atr_pct = sl_atr / sl_close
416
- zeros = np.zeros(240); time_vec = np.arange(1, 241); s_oracle = global_oracle_scores[idx]
 
 
417
  X_H = np.column_stack([
418
  sl_st[:,0], sl_st[:,1], sl_st[:,2], sl_st[:,3], sl_st[:,4],
419
  zeros, atr_pct, norm_pnl, max_pnl_r, zeros, zeros, time_vec, zeros,
@@ -422,22 +455,24 @@ class HeavyDutyBacktester:
422
  max_hydra = 0.0; hydra_time = 0
423
  try:
424
  probs = hydra_models['crash'].predict_proba(X_H)[:, 1]
425
- max_hydra = np.max(probs)
426
  if max_hydra > 0.6:
427
  t = np.argmax(probs)
428
  hydra_time = int(fast_1m['timestamp'][idx + t])
429
  except: pass
430
- max_v2 = np.max(global_v2_scores[idx:idx+240])
 
431
  v2_time = 0
432
  if max_v2 > 0.8:
433
  t2 = np.argmax(global_v2_scores[idx:idx+240])
434
  v2_time = int(fast_1m['timestamp'][idx + t2])
 
435
  ai_results.append({
436
  'timestamp': int(fast_1m['timestamp'][idx]),
437
  'symbol': sym, 'close': entry,
438
- 'real_titan': global_titan_scores[idx],
439
  'oracle_conf': s_oracle,
440
- 'sniper_score': global_sniper_scores[idx],
441
  'risk_hydra_crash': max_hydra, 'time_hydra_crash': hydra_time,
442
  'risk_legacy_v2': max_v2, 'time_legacy_panic': v2_time,
443
  'signal_type': 'BREAKOUT', 'l1_score': 50.0
@@ -468,114 +503,70 @@ class HeavyDutyBacktester:
468
  except: pass
469
  if not data: return []
470
 
471
- # ✅ [GEM-FIX] Reset Index to avoid 'Truth value' error
472
  df = pd.concat(data).sort_values('timestamp').reset_index(drop=True)
473
 
474
  ts = df['timestamp'].values
475
  close = df['close'].values.astype(float)
476
  sym = df['symbol'].values
 
477
 
478
- # Map symbols to integers
479
- u_syms = np.unique(sym)
480
- sym_map = {s: i for i, s in enumerate(u_syms)}
481
- sym_id = np.array([sym_map[s] for s in sym])
482
-
483
- # Extract features as pure numpy arrays (scalar safety)
484
  oracle = df['oracle_conf'].values.astype(float)
485
  sniper = df['sniper_score'].values.astype(float)
486
  hydra = df['risk_hydra_crash'].values.astype(float)
487
  titan = df['real_titan'].values.astype(float)
488
  l1 = df['l1_score'].values.astype(float)
489
-
490
- # Handle Legacy (fill 0 if missing)
491
  legacy_v2 = df['risk_legacy_v2'].values.astype(float) if 'risk_legacy_v2' in df else np.zeros(len(df))
492
-
493
- # Extra: Hydra Time (for expiry check)
494
- h_times = df['time_hydra_crash'].values.astype(int)
495
 
496
  N = len(ts)
497
  print(f" 🚀 [System] Testing {len(combinations_batch)} configs on {N} candles...", flush=True)
498
 
499
  res = []
500
  for cfg in combinations_batch:
501
- pos = {}
502
- log = []
503
- bal = float(initial_capital)
504
- alloc = 0.0
505
 
506
- # Pre-calc mask (Boolean Array)
507
- mask = (l1 >= cfg['l1_thresh']) & \
508
- (oracle >= cfg['oracle_thresh']) & \
509
- (sniper >= cfg['sniper_thresh']) & \
510
- (titan >= 0.55)
511
 
512
- # Loop
513
  for i in range(N):
514
- s = sym_id[i]
515
- p = float(close[i])
516
- curr_t = ts[i]
517
 
518
- # 1. Exit Logic
519
  if s in pos:
520
  entry_p, h_risk_val, size_val, h_time_val = pos[s]
521
 
522
- # Explicit Scalar bools
523
  crash_hydra = bool(h_risk_val > cfg['hydra_thresh'])
524
-
525
- # Logic: If current time > crash time prediction, signal is stale?
526
- # Or if prediction was for a future time?
527
- # Assuming h_time_val is the timestamp of predicted crash
528
  time_match = bool(h_time_val > 0 and curr_t >= h_time_val)
529
-
530
- # Legacy Logic (Global array check)
531
- # Note: Legacy array corresponds to candle index, but here we iterate sorted time
532
- # We need to trust the backtest signal 'risk_legacy_v2' is aligned.
533
- # Yes, it comes from df row 'i'.
534
  panic_legacy = bool(legacy_v2[i] > cfg['legacy_thresh'])
535
-
536
  pnl = (p - entry_p) / entry_p
537
 
538
- # Combined Exit
539
- # Exit if: Hydra Crash AND Time Match OR Legacy Panic OR TP/SL
540
- should_exit = (crash_hydra and time_match) or panic_legacy or (pnl > 0.04) or (pnl < -0.02)
541
-
542
- if should_exit:
543
  realized = pnl - (fees_pct * 2)
544
  bal += size_val * (1.0 + realized)
545
  alloc -= size_val
546
  del pos[s]
547
  log.append({'pnl': realized})
548
 
549
- # 2. Entry Logic
550
- # Use scalar boolean from mask
551
  if len(pos) < max_slots and bool(mask[i]):
552
  if s not in pos and bal >= 5.0:
553
  size = min(10.0, bal * 0.98)
554
- # Store: Entry, HydraRisk, Size, HydraTime
555
  pos[s] = (p, hydra[i], size, h_times[i])
556
- bal -= size
557
- alloc += size
558
 
559
  final_bal = bal + alloc
560
  profit = final_bal - initial_capital
561
-
562
- # Stats
563
  tot = len(log)
564
  winning = [x for x in log if x['pnl'] > 0]
565
  losing = [x for x in log if x['pnl'] <= 0]
566
-
567
- win_count = len(winning)
568
- loss_count = len(losing)
569
  win_rate = (win_count/tot*100) if tot > 0 else 0.0
570
-
571
  avg_win = np.mean([x['pnl'] for x in winning]) if winning else 0.0
572
  avg_loss = np.mean([x['pnl'] for x in losing]) if losing else 0.0
573
-
574
  gross_p = sum([x['pnl'] for x in winning])
575
  gross_l = abs(sum([x['pnl'] for x in losing]))
576
  profit_factor = (gross_p / gross_l) if gross_l > 0 else 99.9
577
-
578
- # Streaks
579
  max_win_s = 0; max_loss_s = 0; curr_w = 0; curr_l = 0
580
  for t in log:
581
  if t['pnl'] > 0:
@@ -592,9 +583,7 @@ class HeavyDutyBacktester:
592
  'avg_win': avg_win, 'avg_loss': avg_loss,
593
  'max_win_streak': max_win_s, 'max_loss_streak': max_loss_s,
594
  'profit_factor': profit_factor,
595
- 'consensus_agreement_rate': 0.0,
596
- 'high_consensus_win_rate': 0.0,
597
- 'high_consensus_avg_pnl': 0.0
598
  })
599
  return res
600
 
@@ -602,29 +591,23 @@ class HeavyDutyBacktester:
602
  await self.generate_truth_data()
603
  oracle_r = np.linspace(0.4, 0.7, 3); sniper_r = np.linspace(0.4, 0.7, 3)
604
  hydra_r = [0.85, 0.95]; l1_r = [10.0]
605
-
606
  combos = []
607
  for o, s, h, l1 in itertools.product(oracle_r, sniper_r, hydra_r, l1_r):
608
  combos.append({
609
  'w_titan': 0.4, 'w_struct': 0.3, 'thresh': l1, 'l1_thresh': l1,
610
  'oracle_thresh': o, 'sniper_thresh': s, 'hydra_thresh': h, 'legacy_thresh': 0.95
611
  })
612
-
613
  files = glob.glob(os.path.join(CACHE_DIR, "*.pkl"))
614
  results_list = self._worker_optimize(combos, files, self.INITIAL_CAPITAL, self.TRADING_FEES, self.MAX_SLOTS)
615
  if not results_list: return None, {'net_profit': 0.0, 'win_rate': 0.0}
616
-
617
  results_list.sort(key=lambda x: x['net_profit'], reverse=True)
618
  best = results_list[0]
619
-
620
- # Auto-Diagnosis
621
  diag = []
622
  if best['total_trades'] > 2000 and best['net_profit'] < 10: diag.append("⚠️ Overtrading")
623
  if best['win_rate'] > 55 and best['net_profit'] < 0: diag.append("⚠️ Fee Burn")
624
  if abs(best['avg_loss']) > best['avg_win']: diag.append("⚠️ Risk/Reward Inversion")
625
  if best['max_loss_streak'] > 10: diag.append("⚠️ Consecutive Loss Risk")
626
  if not diag: diag.append("✅ System Healthy")
627
-
628
  print("\n" + "="*60)
629
  print(f"🏆 CHAMPION REPORT [{target_regime}]:")
630
  print(f" 💰 Final Balance: ${best['final_balance']:,.2f}")
@@ -647,21 +630,19 @@ class HeavyDutyBacktester:
647
  return best['config'], best
648
 
649
  async def run_strategic_optimization_task():
650
- print("\n🧪 [STRATEGIC BACKTEST] Full Spectrum Mode...")
651
  r2 = R2Service(); dm = DataManager(None, None, r2); proc = MLProcessor(dm)
652
  try:
653
  await dm.initialize(); await proc.initialize()
654
  if proc.guardian_hydra: proc.guardian_hydra.set_silent_mode(True)
655
  hub = AdaptiveHub(r2); await hub.initialize()
656
  opt = HeavyDutyBacktester(dm, proc)
657
-
658
  scenarios = [
659
  {"regime": "BULL", "start": "2024-01-01", "end": "2024-03-30"},
660
  {"regime": "BEAR", "start": "2023-08-01", "end": "2023-09-15"},
661
  {"regime": "DEAD", "start": "2023-06-01", "end": "2023-08-01"},
662
  {"regime": "RANGE", "start": "2024-07-01", "end": "2024-09-30"}
663
  ]
664
-
665
  for s in scenarios:
666
  opt.set_date_range(s["start"], s["end"])
667
  best_cfg, best_stats = await opt.run_optimization(s["regime"])
 
1
  # ============================================================
2
+ # 🧪 backtest_engine.py (V141.0 - GEM-Architect: Scalar Enforcement)
3
  # ============================================================
4
 
5
  import asyncio
 
47
  return ((x - r) / s).fillna(0)
48
 
49
  def _revive_score_distribution(scores):
50
+ scores = np.array(scores, dtype=np.float32).flatten() # ✅ Force Flatten
51
  if len(scores) < 10: return scores
52
  std = np.std(scores)
53
  if std < 0.05:
 
73
  s = np.nanstd(x, axis=0) + 1e-9
74
  return np.nan_to_num((x - m) / s, nan=0.0)
75
 
76
+ # ============================================================
77
+ # 🧩 PATTERN RECOGNITION HELPER
78
+ # ============================================================
79
  def _transform_window_for_pattern(df_window):
80
  try:
81
  c = df_window['close'].values.astype('float32')
 
83
  h = df_window['high'].values.astype('float32')
84
  l = df_window['low'].values.astype('float32')
85
  v = df_window['volume'].values.astype('float32')
86
+
87
  base = np.stack([o, h, l, c, v], axis=1)
88
  base_z = _zv(base)
89
  lr = np.zeros_like(c); lr[1:] = np.diff(np.log1p(c))
90
  rng = (h - l) / (c + 1e-9)
91
  extra = np.stack([lr, rng], axis=1)
92
  extra_z = _zv(extra)
93
+
94
  def _ema(arr, n): return pd.Series(arr).ewm(span=n, adjust=False).mean().values
95
  ema9 = _ema(c, 9); ema21 = _ema(c, 21); ema50 = _ema(c, 50); ema200 = _ema(c, 200)
96
  slope21 = np.gradient(ema21); slope50 = np.gradient(ema50)
 
101
  roll_down = pd.Series(down).abs().ewm(alpha=1/14, adjust=False).mean().values
102
  rs = roll_up / (roll_down + 1e-9)
103
  rsi = 100.0 - (100.0 / (1.0 + rs))
104
+
105
  indicators = np.stack([ema9, ema21, ema50, ema200, slope21, slope50, rsi], axis=1)
106
  X_seq = np.concatenate([base_z, extra_z, _zv(indicators)], axis=1)
107
  X_flat = X_seq.flatten()
 
120
  self.INITIAL_CAPITAL = 10.0
121
  self.TRADING_FEES = 0.001
122
  self.MAX_SLOTS = 4
123
+
124
  self.TARGET_COINS = [
125
+ 'SOL/USDT' ]
126
+
127
  self.force_start_date = None
128
  self.force_end_date = None
129
+
130
  if not os.path.exists(CACHE_DIR): os.makedirs(CACHE_DIR)
131
+ print(f"🧪 [Backtest V141.0] Scalar Enforcement Mode.")
132
 
133
  def set_date_range(self, start_str, end_str):
134
  self.force_start_date = start_str
 
145
  current += duration_per_batch
146
  all_candles = []
147
  sem = asyncio.Semaphore(20)
148
+
149
  async def _fetch_batch(timestamp):
150
  async with sem:
151
  for _ in range(3):
 
153
  return await self.dm.exchange.fetch_ohlcv(sym, '1m', since=timestamp, limit=limit)
154
  except: await asyncio.sleep(0.5)
155
  return []
156
+
157
  chunk_size = 50
158
  for i in range(0, len(tasks), chunk_size):
159
  chunk_tasks = tasks[i:i + chunk_size]
 
161
  results = await asyncio.gather(*futures)
162
  for res in results:
163
  if res: all_candles.extend(res)
164
+
165
  if not all_candles: return None
166
  df = pd.DataFrame(all_candles, columns=['timestamp', 'o', 'h', 'l', 'c', 'v'])
167
  df.drop_duplicates('timestamp', inplace=True)
 
170
  print(f" ✅ Downloaded {len(df)} candles.", flush=True)
171
  return df.values.tolist()
172
 
173
+ # ==============================================================
174
+ # 🏎️ VECTORIZED INDICATORS (EXACT MATCH TO LIVE SYSTEM)
175
+ # ==============================================================
176
  def _calculate_indicators_vectorized(self, df, timeframe='1m'):
177
  cols = ['close', 'high', 'low', 'volume', 'open']
178
  for c in cols: df[c] = df[c].astype(np.float64)
179
  idx = df.index
180
+
181
+ # --- TITAN FEATURES ---
182
  df['RSI'] = safe_ta(ta.rsi(df['close'], length=14), idx, 50)
183
  macd = ta.macd(df['close'])
184
  if macd is not None:
 
189
  adx = ta.adx(df['high'], df['low'], df['close'], length=14)
190
  if adx is not None: df['ADX'] = safe_ta(adx.iloc[:, 0], idx, 0)
191
  else: df['ADX'] = 0.0
192
+ if timeframe == '1d':
193
+ df['Trend_Strong'] = np.where(df['ADX'] > 25, 1.0, 0.0)
194
+
195
  for p in [9, 21, 50, 200]:
196
  ema = safe_ta(ta.ema(df['close'], length=p), idx, 0)
197
  df[f'EMA_{p}_dist'] = ((df['close'] / ema.replace(0, np.nan)) - 1).fillna(0)
198
  df[f'ema{p}'] = ema
199
+
200
  df['ema20'] = safe_ta(ta.ema(df['close'], length=20), idx, df['close'])
201
+
202
  bb = ta.bbands(df['close'], length=20, std=2.0)
203
  if bb is not None:
204
  w = ((bb.iloc[:, 2] - bb.iloc[:, 0]) / bb.iloc[:, 1].replace(0, np.nan)).fillna(0)
205
  p = ((df['close'] - bb.iloc[:, 0]) / (bb.iloc[:, 2] - bb.iloc[:, 0]).replace(0, np.nan)).fillna(0)
206
  df['BB_w'] = w; df['BB_p'] = p; df['bb_width'] = w
207
  else: df['BB_w'] = 0; df['BB_p'] = 0; df['bb_width'] = 0
208
+
209
  df['MFI'] = safe_ta(ta.mfi(df['high'], df['low'], df['close'], df['volume'], length=14), idx, 50)
210
  vwap = ta.vwap(df['high'], df['low'], df['close'], df['volume'])
211
  if vwap is not None:
212
  df['VWAP_dist'] = ((df['close'] / vwap.replace(0, np.nan)) - 1).fillna(0)
213
  df['vwap'] = vwap
214
  else: df['VWAP_dist'] = 0.0; df['vwap'] = df['close']
215
+
216
  df['atr'] = safe_ta(ta.atr(df['high'], df['low'], df['close'], length=14), idx, 0)
217
  df['atr_pct'] = (df['atr'] / df['close'].replace(0, np.nan)).fillna(0)
218
  df['ATR_pct'] = df['atr_pct']
219
 
220
+ # --- SNIPER FEATURES ---
221
  if timeframe == '1m':
222
  df['return_1m'] = df['close'].pct_change().fillna(0)
223
  df['return_3m'] = df['close'].pct_change(3).fillna(0)
 
256
  df['rv_gk'] = _z_roll(rv_gk)
257
  df['L_score'] = (df['vol_zscore_50'] - df['amihud'] - df['roll_spread'] - df['rv_gk'].abs() - df['vwap_dev'].abs() + df['ofi']).fillna(0)
258
 
259
+ # --- EXTRAS ---
260
  df['slope'] = safe_ta(ta.slope(df['close'], length=7), idx, 0)
261
  vol_mean = df['volume'].rolling(20).mean()
262
  vol_std = df['volume'].rolling(20).std().replace(0, np.nan)
263
  df['vol_z'] = ((df['volume'] - vol_mean) / vol_std).fillna(0)
264
  df['rel_vol'] = df['volume'] / (df['volume'].rolling(50).mean() + 1e-9)
265
  df['log_ret'] = np.log(df['close'] / df['close'].shift(1).replace(0, np.nan)).fillna(0)
266
+ roll_max = df['high'].rolling(50).max(); roll_min = df['low'].rolling(50).min()
 
267
  diff = (roll_max - roll_min).replace(0, 1e-9)
268
  df['fib_pos'] = ((df['close'] - roll_min) / diff).fillna(0.5)
269
  e20_s = df['ema20'].shift(5).replace(0, np.nan)
 
275
  e200 = safe_ta(ta.ema(df['close'], length=200), idx, df['close'])
276
  df['ema200'] = e200
277
  df['dist_ema200'] = ((df['close'] - e200) / e200.replace(0, np.nan)).fillna(0)
278
+
279
  if timeframe == '1m':
280
  for lag in [1, 2, 3, 5, 10, 20]:
281
  df[f'log_ret_lag_{lag}'] = df['log_ret'].shift(lag).fillna(0)
282
  df[f'rsi_lag_{lag}'] = (df['RSI'].shift(lag) / 100.0).fillna(0.5)
283
  df[f'fib_pos_lag_{lag}'] = df['fib_pos'].shift(lag).fillna(0.5)
284
  df[f'volatility_lag_{lag}'] = df['volatility'].shift(lag).fillna(0)
285
+
286
  df.fillna(0, inplace=True)
287
  return df
288
 
289
+ # ==============================================================
290
+ # 🧠 CPU PROCESSING (GLOBAL INFERENCE)
291
+ # ==============================================================
292
  async def _process_data_in_memory(self, sym, candles, start_ms, end_ms):
293
  safe_sym = sym.replace('/', '_')
294
  period_suffix = f"{start_ms}_{end_ms}"
295
  scores_file = f"{CACHE_DIR}/{safe_sym}_{period_suffix}_scores.pkl"
296
+
297
  if os.path.exists(scores_file):
298
  print(f" 📂 [{sym}] Data Exists -> Skipping.")
299
  return
 
361
  elif target_arr and feat in ['open','high','low','close','volume']: t_vecs.append(target_arr[feat][target_map])
362
  else: t_vecs.append(np.zeros(len(arr_ts_1m)))
363
  X_TITAN = np.column_stack(t_vecs)
364
+ preds_t = titan_model.predict(xgb.DMatrix(X_TITAN, feature_names=titan_cols))
365
+ global_titan_scores = _revive_score_distribution(preds_t) # ✅ Flattened inside
366
  except: pass
367
 
368
  # B. SNIPER (Global)
 
413
  X_V2 = np.column_stack([l_log, l_rsi, l_fib, l_vol, l5_log, l5_rsi, l5_fib, l5_trd, l15_log, l15_rsi, l15_fib618, l15_trd, *lags])
414
  preds = legacy_v2.predict(xgb.DMatrix(X_V2))
415
  global_v2_scores = preds[:, 2] if len(preds.shape) > 1 else preds
416
+ global_v2_scores = global_v2_scores.flatten() # ✅ Safety Flatten
417
  except: pass
418
 
419
  # Filter
 
438
  for idx in chunk_idxs:
439
  sl_st = h_static[idx:idx+240]
440
  sl_close = sl_st[:, 6]; sl_atr = sl_st[:, 5]
441
+ entry = float(fast_1m['close'][idx]) # ✅ Scalar
442
  dist = np.maximum(1.5 * sl_atr, entry * 0.015)
443
  pnl = sl_close - entry
444
  norm_pnl = pnl / dist
445
  max_pnl_r = (np.maximum.accumulate(sl_close) - entry) / dist
446
  atr_pct = sl_atr / sl_close
447
+ zeros = np.zeros(240); time_vec = np.arange(1, 241)
448
+ s_oracle = float(global_oracle_scores[idx]) # ✅ Scalar
449
+
450
  X_H = np.column_stack([
451
  sl_st[:,0], sl_st[:,1], sl_st[:,2], sl_st[:,3], sl_st[:,4],
452
  zeros, atr_pct, norm_pnl, max_pnl_r, zeros, zeros, time_vec, zeros,
 
455
  max_hydra = 0.0; hydra_time = 0
456
  try:
457
  probs = hydra_models['crash'].predict_proba(X_H)[:, 1]
458
+ max_hydra = float(np.max(probs)) # ✅ Scalar
459
  if max_hydra > 0.6:
460
  t = np.argmax(probs)
461
  hydra_time = int(fast_1m['timestamp'][idx + t])
462
  except: pass
463
+
464
+ max_v2 = float(np.max(global_v2_scores[idx:idx+240])) # ✅ Scalar
465
  v2_time = 0
466
  if max_v2 > 0.8:
467
  t2 = np.argmax(global_v2_scores[idx:idx+240])
468
  v2_time = int(fast_1m['timestamp'][idx + t2])
469
+
470
  ai_results.append({
471
  'timestamp': int(fast_1m['timestamp'][idx]),
472
  'symbol': sym, 'close': entry,
473
+ 'real_titan': float(global_titan_scores[idx]), # ✅ Scalar
474
  'oracle_conf': s_oracle,
475
+ 'sniper_score': float(global_sniper_scores[idx]), # ✅ Scalar
476
  'risk_hydra_crash': max_hydra, 'time_hydra_crash': hydra_time,
477
  'risk_legacy_v2': max_v2, 'time_legacy_panic': v2_time,
478
  'signal_type': 'BREAKOUT', 'l1_score': 50.0
 
503
  except: pass
504
  if not data: return []
505
 
506
+ # ✅ Reset Index to ensure aligned access
507
  df = pd.concat(data).sort_values('timestamp').reset_index(drop=True)
508
 
509
  ts = df['timestamp'].values
510
  close = df['close'].values.astype(float)
511
  sym = df['symbol'].values
512
+ u_syms = np.unique(sym); sym_map = {s: i for i, s in enumerate(u_syms)}; sym_id = np.array([sym_map[s] for s in sym])
513
 
514
+ # Explicit Scalar Conversion for Features
 
 
 
 
 
515
  oracle = df['oracle_conf'].values.astype(float)
516
  sniper = df['sniper_score'].values.astype(float)
517
  hydra = df['risk_hydra_crash'].values.astype(float)
518
  titan = df['real_titan'].values.astype(float)
519
  l1 = df['l1_score'].values.astype(float)
 
 
520
  legacy_v2 = df['risk_legacy_v2'].values.astype(float) if 'risk_legacy_v2' in df else np.zeros(len(df))
521
+ h_times = df['time_hydra_crash'].values.astype(np.int64)
 
 
522
 
523
  N = len(ts)
524
  print(f" 🚀 [System] Testing {len(combinations_batch)} configs on {N} candles...", flush=True)
525
 
526
  res = []
527
  for cfg in combinations_batch:
528
+ pos = {}; log = []
529
+ bal = float(initial_capital); alloc = 0.0
 
 
530
 
531
+ # Mask is now purely boolean (True/False)
532
+ mask = (l1 >= cfg['l1_thresh']) & (oracle >= cfg['oracle_thresh']) & (sniper >= cfg['sniper_thresh']) & (titan >= 0.55)
 
 
 
533
 
 
534
  for i in range(N):
535
+ s = sym_id[i]; p = float(close[i]); curr_t = ts[i]
 
 
536
 
 
537
  if s in pos:
538
  entry_p, h_risk_val, size_val, h_time_val = pos[s]
539
 
 
540
  crash_hydra = bool(h_risk_val > cfg['hydra_thresh'])
 
 
 
 
541
  time_match = bool(h_time_val > 0 and curr_t >= h_time_val)
 
 
 
 
 
542
  panic_legacy = bool(legacy_v2[i] > cfg['legacy_thresh'])
 
543
  pnl = (p - entry_p) / entry_p
544
 
545
+ if (crash_hydra and time_match) or panic_legacy or pnl > 0.04 or pnl < -0.02:
 
 
 
 
546
  realized = pnl - (fees_pct * 2)
547
  bal += size_val * (1.0 + realized)
548
  alloc -= size_val
549
  del pos[s]
550
  log.append({'pnl': realized})
551
 
 
 
552
  if len(pos) < max_slots and bool(mask[i]):
553
  if s not in pos and bal >= 5.0:
554
  size = min(10.0, bal * 0.98)
 
555
  pos[s] = (p, hydra[i], size, h_times[i])
556
+ bal -= size; alloc += size
 
557
 
558
  final_bal = bal + alloc
559
  profit = final_bal - initial_capital
 
 
560
  tot = len(log)
561
  winning = [x for x in log if x['pnl'] > 0]
562
  losing = [x for x in log if x['pnl'] <= 0]
563
+ win_count = len(winning); loss_count = len(losing)
 
 
564
  win_rate = (win_count/tot*100) if tot > 0 else 0.0
 
565
  avg_win = np.mean([x['pnl'] for x in winning]) if winning else 0.0
566
  avg_loss = np.mean([x['pnl'] for x in losing]) if losing else 0.0
 
567
  gross_p = sum([x['pnl'] for x in winning])
568
  gross_l = abs(sum([x['pnl'] for x in losing]))
569
  profit_factor = (gross_p / gross_l) if gross_l > 0 else 99.9
 
 
570
  max_win_s = 0; max_loss_s = 0; curr_w = 0; curr_l = 0
571
  for t in log:
572
  if t['pnl'] > 0:
 
583
  'avg_win': avg_win, 'avg_loss': avg_loss,
584
  'max_win_streak': max_win_s, 'max_loss_streak': max_loss_s,
585
  'profit_factor': profit_factor,
586
+ 'consensus_agreement_rate': 0.0, 'high_consensus_win_rate': 0.0, 'high_consensus_avg_pnl': 0.0
 
 
587
  })
588
  return res
589
 
 
591
  await self.generate_truth_data()
592
  oracle_r = np.linspace(0.4, 0.7, 3); sniper_r = np.linspace(0.4, 0.7, 3)
593
  hydra_r = [0.85, 0.95]; l1_r = [10.0]
 
594
  combos = []
595
  for o, s, h, l1 in itertools.product(oracle_r, sniper_r, hydra_r, l1_r):
596
  combos.append({
597
  'w_titan': 0.4, 'w_struct': 0.3, 'thresh': l1, 'l1_thresh': l1,
598
  'oracle_thresh': o, 'sniper_thresh': s, 'hydra_thresh': h, 'legacy_thresh': 0.95
599
  })
 
600
  files = glob.glob(os.path.join(CACHE_DIR, "*.pkl"))
601
  results_list = self._worker_optimize(combos, files, self.INITIAL_CAPITAL, self.TRADING_FEES, self.MAX_SLOTS)
602
  if not results_list: return None, {'net_profit': 0.0, 'win_rate': 0.0}
 
603
  results_list.sort(key=lambda x: x['net_profit'], reverse=True)
604
  best = results_list[0]
 
 
605
  diag = []
606
  if best['total_trades'] > 2000 and best['net_profit'] < 10: diag.append("⚠️ Overtrading")
607
  if best['win_rate'] > 55 and best['net_profit'] < 0: diag.append("⚠️ Fee Burn")
608
  if abs(best['avg_loss']) > best['avg_win']: diag.append("⚠️ Risk/Reward Inversion")
609
  if best['max_loss_streak'] > 10: diag.append("⚠️ Consecutive Loss Risk")
610
  if not diag: diag.append("✅ System Healthy")
 
611
  print("\n" + "="*60)
612
  print(f"🏆 CHAMPION REPORT [{target_regime}]:")
613
  print(f" 💰 Final Balance: ${best['final_balance']:,.2f}")
 
630
  return best['config'], best
631
 
632
  async def run_strategic_optimization_task():
633
+ print("\n🧪 [STRATEGIC BACKTEST] Scalar Enforcement Mode...")
634
  r2 = R2Service(); dm = DataManager(None, None, r2); proc = MLProcessor(dm)
635
  try:
636
  await dm.initialize(); await proc.initialize()
637
  if proc.guardian_hydra: proc.guardian_hydra.set_silent_mode(True)
638
  hub = AdaptiveHub(r2); await hub.initialize()
639
  opt = HeavyDutyBacktester(dm, proc)
 
640
  scenarios = [
641
  {"regime": "BULL", "start": "2024-01-01", "end": "2024-03-30"},
642
  {"regime": "BEAR", "start": "2023-08-01", "end": "2023-09-15"},
643
  {"regime": "DEAD", "start": "2023-06-01", "end": "2023-08-01"},
644
  {"regime": "RANGE", "start": "2024-07-01", "end": "2024-09-30"}
645
  ]
 
646
  for s in scenarios:
647
  opt.set_date_range(s["start"], s["end"])
648
  best_cfg, best_stats = await opt.run_optimization(s["regime"])