Riy777 commited on
Commit
8492f56
·
verified ·
1 Parent(s): 7e33737

Update backtest_engine.py

Browse files
Files changed (1) hide show
  1. backtest_engine.py +148 -80
backtest_engine.py CHANGED
@@ -1,5 +1,5 @@
1
  # ============================================================
2
- # 🧪 backtest_engine.py (V117.1 - GEM-Architect: Speed Demon)
3
  # ============================================================
4
 
5
  import asyncio
@@ -60,7 +60,7 @@ class HeavyDutyBacktester:
60
  else:
61
  os.makedirs(CACHE_DIR)
62
 
63
- print(f"🧪 [Backtest V117.1] Speed Demon Loaded. Cache Flushed. Targets: {len(self.TARGET_COINS)}")
64
 
65
  def set_date_range(self, start_str, end_str):
66
  self.force_start_date = start_str
@@ -197,7 +197,7 @@ class HeavyDutyBacktester:
197
  return df
198
 
199
  # ==============================================================
200
- # 🧠 CPU PROCESSING (OPTIMIZED BATCH INFERENCE)
201
  # ==============================================================
202
  async def _process_data_in_memory(self, sym, candles, start_ms, end_ms):
203
  safe_sym = sym.replace('/', '_')
@@ -248,8 +248,12 @@ class HeavyDutyBacktester:
248
  # 3. Load Models
249
  hydra_models = getattr(self.proc.guardian_hydra, 'models', {}) if self.proc.guardian_hydra else {}
250
  legacy_v2 = getattr(self.proc.guardian_legacy, 'model_v2', None)
 
251
  oracle_dir_model = getattr(self.proc.oracle, 'model_direction', None)
 
 
252
  sniper_models = getattr(self.proc.sniper, 'models', [])
 
253
 
254
  # 4. 🔥 PRE-CALC LEGACY V2 🔥
255
  global_v2_probs = np.zeros(len(fast_1m['close']))
@@ -313,42 +317,62 @@ class HeavyDutyBacktester:
313
  ai_results = []
314
  time_vec = np.arange(1, 241)
315
 
316
- # 🔥 BATCH BUFFERS FOR HYDRA 🔥
317
  hydra_batch_X = []
318
  hydra_batch_indices = []
319
- BATCH_SIZE = 2000
320
 
321
- # Temp results to be filled by batch processing
322
- temp_hydra_results = {} # {idx: (risk, time)}
 
 
 
 
 
 
 
323
 
324
- # Main Loop: Collect Data & Run Simple Models
325
  for i, current_time in enumerate(final_valid_indices):
326
  ts_val = int(current_time.timestamp() * 1000)
327
  idx_1m = np.searchsorted(fast_1m['timestamp'], ts_val)
328
 
329
  if idx_1m < 500 or idx_1m >= len(fast_1m['close']) - 245: continue
330
 
331
- # --- Oracle & Sniper (Keep Per-Instance or could be batched, but they are fast enough usually) ---
332
- # To speed up, we just use defaults if models are missing
333
- oracle_conf = 0.5
334
- sniper_score = 0.5
335
-
336
- # (Simplification: If models exist, run inference. If bottleneck, move to Batch like Hydra)
337
- # For now, leaving Oracle/Sniper as is (they are lighter than Hydra loops)
338
- # ... [Oracle/Sniper code omitted for brevity, assuming minimal impact or similar batching can be applied]
339
- # Re-inserting simple placeholders for speed in this demo if needed,
340
- # BUT let's keep the logic:
341
-
342
  idx_1h = map_1m_to_1h[idx_1m]
343
  idx_15m = map_1m_to_15m[idx_1m]
344
  idx_4h = np.searchsorted(numpy_htf['4h']['timestamp'], ts_val)
345
  if idx_4h >= len(numpy_htf['4h']['close']): idx_4h = len(numpy_htf['4h']['close']) - 1
346
 
347
- if oracle_dir_model:
348
- # Construct vector... (Fast enough for single row usually)
349
- pass # (Assume code from previous block executes here)
350
 
351
- # --- HYDRA PREP (THE BOTTLENECK FIX) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  if hydra_models and global_hydra_static is not None:
353
  start_idx = idx_1m + 1
354
  end_idx = start_idx + 240
@@ -356,7 +380,6 @@ class HeavyDutyBacktester:
356
  sl_static = global_hydra_static[start_idx:end_idx]
357
  entry_price = fast_1m['close'][idx_1m]
358
 
359
- # Vectorized Matrix Construction for the 240 window
360
  sl_close = sl_static[:, 6]
361
  sl_atr = sl_static[:, 5]
362
  sl_dist = np.maximum(1.5 * sl_atr, entry_price * 0.015)
@@ -368,11 +391,12 @@ class HeavyDutyBacktester:
368
  sl_atr_pct = sl_atr / sl_close
369
 
370
  zeros = np.zeros(240)
371
- ones_oracle = np.full(240, oracle_conf)
 
 
372
  ones_l2 = np.full(240, 0.7)
373
  ones_target = np.full(240, 3.0)
374
 
375
- # Construct the matrix for this candidate
376
  X_cand = np.column_stack([
377
  sl_static[:, 0], sl_static[:, 1], sl_static[:, 2],
378
  sl_static[:, 3], sl_static[:, 4],
@@ -384,44 +408,84 @@ class HeavyDutyBacktester:
384
  ])
385
 
386
  hydra_batch_X.append(X_cand)
387
- hydra_batch_indices.append(len(ai_results)) # Track which result this belongs to
388
-
389
- # Trigger Batch Prediction if full
390
- if len(hydra_batch_X) >= BATCH_SIZE:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  big_X = np.vstack(hydra_batch_X)
392
  try:
393
- # 🔥 SINGLE CALL FOR 2000 CANDIDATES 🔥
394
  preds = hydra_models['crash'].predict_proba(big_X)[:, 1]
395
- # Split results back
396
  for b_i, res_idx in enumerate(hydra_batch_indices):
397
  p_slice = preds[b_i*240 : (b_i+1)*240]
398
  max_p = np.max(p_slice)
399
  c_idx = np.where(p_slice > 0.6)[0]
400
- c_time = int(fast_1m['timestamp'][fast_1m['timestamp'].searchsorted(ts_val) + 1 + c_idx[0]]) if len(c_idx) > 0 else 0
401
  temp_hydra_results[res_idx] = (max_p, c_time)
402
  except: pass
403
  hydra_batch_X = []
404
  hydra_batch_indices = []
405
 
406
- # Legacy V2 (Fast Lookup)
407
- max_legacy_v2 = 0.0; legacy_panic_time = 0
408
- if legacy_v2:
409
- start_idx = idx_1m + 1
410
- probs_slice = global_v2_probs[start_idx:start_idx+240]
411
- max_legacy_v2 = np.max(probs_slice)
412
- panic_indices = np.where(probs_slice > 0.8)[0]
413
- if len(panic_indices) > 0:
414
- legacy_panic_time = int(fast_1m['timestamp'][start_idx + panic_indices[0]])
 
415
 
416
- ai_results.append({
417
- 'timestamp': ts_val, 'symbol': sym, 'close': entry_price,
418
- 'real_titan': 0.6, 'oracle_conf': oracle_conf, 'sniper_score': sniper_score,
419
- 'risk_hydra_crash': 0.0, 'time_hydra_crash': 0, # To be filled
420
- 'risk_legacy_v2': max_legacy_v2, 'time_legacy_panic': legacy_panic_time,
421
- 'signal_type': 'BREAKOUT', 'l1_score': 50.0
422
- })
 
 
 
423
 
424
- # Process remaining Hydra batch
425
  if hydra_batch_X:
426
  big_X = np.vstack(hydra_batch_X)
427
  try:
@@ -434,10 +498,29 @@ class HeavyDutyBacktester:
434
  temp_hydra_results[res_idx] = (max_p, c_time)
435
  except: pass
436
 
437
- # Fill Hydra Results
438
- for idx, (risk, t_crash) in temp_hydra_results.items():
439
- ai_results[idx]['risk_hydra_crash'] = risk
440
- ai_results[idx]['time_hydra_crash'] = t_crash
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
441
 
442
  dt = time.time() - t0
443
  if ai_results:
@@ -483,7 +566,6 @@ class HeavyDutyBacktester:
483
  global_df.sort_values('timestamp', inplace=True)
484
 
485
  # 🚀 CONVERT TO NUMPY ARRAYS FOR BLAZING SPEED 🚀
486
- # This removes pandas overhead from the inner loop
487
  arr_ts = global_df['timestamp'].values
488
  arr_close = global_df['close'].values.astype(np.float64)
489
  arr_symbol = global_df['symbol'].values
@@ -493,7 +575,6 @@ class HeavyDutyBacktester:
493
  arr_hydra_time = global_df['time_hydra_crash'].values.astype(np.int64)
494
  arr_titan = global_df['real_titan'].values.astype(np.float64)
495
 
496
- # Pre-map symbols to integers for faster dictionary lookups
497
  unique_syms = np.unique(arr_symbol)
498
  sym_map = {s: i for i, s in enumerate(unique_syms)}
499
  arr_sym_int = np.array([sym_map[s] for s in arr_symbol], dtype=np.int32)
@@ -506,7 +587,6 @@ class HeavyDutyBacktester:
506
  start_time = time.time()
507
 
508
  for idx, config in enumerate(combinations_batch):
509
- # Progress Logging (Every 10 combos)
510
  if idx > 0 and idx % 10 == 0:
511
  elapsed = time.time() - start_time
512
  avg_time = elapsed / idx
@@ -514,45 +594,32 @@ class HeavyDutyBacktester:
514
  sys.stdout.write(f"\r ⚙️ Progress: {idx}/{total_combos} ({idx/total_combos:.1%}) | ETA: {rem_time:.0f}s")
515
  sys.stdout.flush()
516
 
517
- # --- Logic Core ---
518
  wallet_bal = initial_capital
519
  wallet_alloc = 0.0
520
- # positions: key=sym_int, val=[entry, size, risk_h, time_h, score]
521
  positions = {}
522
- trades_log = [] # [pnl, score]
523
 
524
  oracle_thresh = config.get('oracle_thresh', 0.6)
525
  sniper_thresh = config.get('sniper_thresh', 0.4)
526
  hydra_thresh = config['hydra_thresh']
527
 
528
- # Pre-calculate entry candidates mask for this config
529
- # (Vectorized check is 100x faster than checking inside loop)
530
  mask_buy = (arr_oracle >= oracle_thresh) & (arr_sniper >= sniper_thresh)
531
 
532
  peak_bal = initial_capital
533
  max_dd = 0.0
534
-
535
- # 🚀 OPTIMIZED TIME LOOP 🚀
536
- # We iterate through indices. Since data is sorted by time,
537
- # we can process linearly.
538
- # Warning: Multiple symbols share same timestamp.
539
- # Group logic simulated by linear scan.
540
-
541
- current_ts = arr_ts[0]
542
 
543
- # Simple linear iteration is safest to preserve logic without complex groupby
544
- # Speedup comes from numpy access vs dataframe access
545
  for i in range(total_len):
546
  ts = arr_ts[i]
547
  sym_id = arr_sym_int[i]
548
  price = arr_close[i]
549
 
550
- # Check Exits first (for open positions)
551
  if sym_id in positions:
552
- pos = positions[sym_id] # [entry, size, risk, time, score]
553
  entry = pos[0]
554
 
555
- # Hydra Check
556
  h_risk = pos[2]
557
  h_time = pos[3]
558
  is_crash = (h_risk > hydra_thresh) and (h_time > 0) and (ts >= h_time)
@@ -565,7 +632,6 @@ class HeavyDutyBacktester:
565
  trades_log.append((pnl, pos[4]))
566
  del positions[sym_id]
567
 
568
- # Update DD
569
  tot = wallet_bal + wallet_alloc
570
  if tot > peak_bal: peak_bal = tot
571
  else:
@@ -573,19 +639,17 @@ class HeavyDutyBacktester:
573
  if dd > max_dd: max_dd = dd
574
 
575
  # Check Entries
576
- # Only if we have space AND signal matches
577
  if len(positions) < max_slots:
578
  if mask_buy[i]:
579
  if sym_id not in positions:
580
  if wallet_bal >= 10.0:
581
  cons_score = (arr_titan[i] + arr_oracle[i] + arr_sniper[i]) / 3.0
582
- # Enter
583
  size = 10.0
584
  positions[sym_id] = [price, size, arr_hydra_risk[i], arr_hydra_time[i], cons_score]
585
  wallet_bal -= size
586
  wallet_alloc += size
587
 
588
- # Final Stats Calc
589
  final_bal = wallet_bal + wallet_alloc
590
  net_profit = final_bal - initial_capital
591
  total_t = len(trades_log)
@@ -638,7 +702,7 @@ class HeavyDutyBacktester:
638
  'high_consensus_avg_pnl': hc_avg_pnl
639
  })
640
 
641
- print("") # New line after progress bar
642
  return results
643
 
644
  async def run_optimization(self, target_regime="RANGE"):
@@ -701,8 +765,12 @@ async def run_strategic_optimization_task():
701
  hub = AdaptiveHub(r2); await hub.initialize()
702
  optimizer = HeavyDutyBacktester(dm, proc)
703
 
 
704
  scenarios = [
705
  {"regime": "BULL", "start": "2024-01-01", "end": "2024-03-30"},
 
 
 
706
  ]
707
 
708
  for scen in scenarios:
 
1
  # ============================================================
2
+ # 🧪 backtest_engine.py (V117.2 - GEM-Architect: Speed + Accuracy)
3
  # ============================================================
4
 
5
  import asyncio
 
60
  else:
61
  os.makedirs(CACHE_DIR)
62
 
63
+ print(f"🧪 [Backtest V117.2] High-Fidelity Speed Mode. Targets: {len(self.TARGET_COINS)}")
64
 
65
  def set_date_range(self, start_str, end_str):
66
  self.force_start_date = start_str
 
197
  return df
198
 
199
  # ==============================================================
200
+ # 🧠 CPU PROCESSING (OPTIMIZED BATCH INFERENCE V2)
201
  # ==============================================================
202
  async def _process_data_in_memory(self, sym, candles, start_ms, end_ms):
203
  safe_sym = sym.replace('/', '_')
 
248
  # 3. Load Models
249
  hydra_models = getattr(self.proc.guardian_hydra, 'models', {}) if self.proc.guardian_hydra else {}
250
  legacy_v2 = getattr(self.proc.guardian_legacy, 'model_v2', None)
251
+
252
  oracle_dir_model = getattr(self.proc.oracle, 'model_direction', None)
253
+ oracle_cols = getattr(self.proc.oracle, 'feature_cols', [])
254
+
255
  sniper_models = getattr(self.proc.sniper, 'models', [])
256
+ sniper_cols = getattr(self.proc.sniper, 'feature_names', [])
257
 
258
  # 4. 🔥 PRE-CALC LEGACY V2 🔥
259
  global_v2_probs = np.zeros(len(fast_1m['close']))
 
317
  ai_results = []
318
  time_vec = np.arange(1, 241)
319
 
320
+ # 🔥 BATCH BUFFERS 🔥
321
  hydra_batch_X = []
322
  hydra_batch_indices = []
 
323
 
324
+ oracle_batch_X = []
325
+ sniper_batch_X = []
326
+
327
+ BATCH_SIZE = 5000 # Increased batch size for efficiency
328
+
329
+ # Temp results stores
330
+ temp_hydra_results = {}
331
+ temp_oracle_results = {}
332
+ temp_sniper_results = {}
333
 
334
+ # Main Loop: Collect Data
335
  for i, current_time in enumerate(final_valid_indices):
336
  ts_val = int(current_time.timestamp() * 1000)
337
  idx_1m = np.searchsorted(fast_1m['timestamp'], ts_val)
338
 
339
  if idx_1m < 500 or idx_1m >= len(fast_1m['close']) - 245: continue
340
 
 
 
 
 
 
 
 
 
 
 
 
341
  idx_1h = map_1m_to_1h[idx_1m]
342
  idx_15m = map_1m_to_15m[idx_1m]
343
  idx_4h = np.searchsorted(numpy_htf['4h']['timestamp'], ts_val)
344
  if idx_4h >= len(numpy_htf['4h']['close']): idx_4h = len(numpy_htf['4h']['close']) - 1
345
 
346
+ current_res_idx = len(ai_results) # Index in result list
 
 
347
 
348
+ # --- 🔮 BATCH ORACLE ---
349
+ if oracle_dir_model:
350
+ o_vec = []
351
+ for col in oracle_cols:
352
+ val = 0.0
353
+ if col.startswith('1h_'): val = numpy_htf['1h'].get(col[3:], [0])[idx_1h]
354
+ elif col.startswith('15m_'): val = numpy_htf['15m'].get(col[4:], [0])[idx_15m]
355
+ elif col.startswith('4h_'): val = numpy_htf['4h'].get(col[3:], [0])[idx_4h]
356
+ elif col == 'sim_titan_score': val = 0.6
357
+ elif col == 'sim_mc_score': val = 0.5
358
+ elif col == 'sim_pattern_score': val = 0.5
359
+ o_vec.append(val)
360
+ oracle_batch_X.append(o_vec)
361
+ else:
362
+ temp_oracle_results[current_res_idx] = 0.5
363
+
364
+ # --- 🔫 BATCH SNIPER ---
365
+ if sniper_models:
366
+ s_vec = []
367
+ for col in sniper_cols:
368
+ if col in fast_1m: s_vec.append(fast_1m[col][idx_1m])
369
+ elif col == 'L_score': s_vec.append(fast_1m.get('vol_zscore_50', [0])[idx_1m])
370
+ else: s_vec.append(0.0)
371
+ sniper_batch_X.append(s_vec)
372
+ else:
373
+ temp_sniper_results[current_res_idx] = 0.5
374
+
375
+ # --- 🐲 BATCH HYDRA ---
376
  if hydra_models and global_hydra_static is not None:
377
  start_idx = idx_1m + 1
378
  end_idx = start_idx + 240
 
380
  sl_static = global_hydra_static[start_idx:end_idx]
381
  entry_price = fast_1m['close'][idx_1m]
382
 
 
383
  sl_close = sl_static[:, 6]
384
  sl_atr = sl_static[:, 5]
385
  sl_dist = np.maximum(1.5 * sl_atr, entry_price * 0.015)
 
391
  sl_atr_pct = sl_atr / sl_close
392
 
393
  zeros = np.zeros(240)
394
+ # Note: We use 0.5 as placeholder for Oracle in Hydra until we run Oracle,
395
+ # but for speed we assume average conf for risk check
396
+ ones_oracle = np.full(240, 0.6)
397
  ones_l2 = np.full(240, 0.7)
398
  ones_target = np.full(240, 3.0)
399
 
 
400
  X_cand = np.column_stack([
401
  sl_static[:, 0], sl_static[:, 1], sl_static[:, 2],
402
  sl_static[:, 3], sl_static[:, 4],
 
408
  ])
409
 
410
  hydra_batch_X.append(X_cand)
411
+ hydra_batch_indices.append(current_res_idx)
412
+
413
+ # Placeholder Result
414
+ ai_results.append({
415
+ 'timestamp': ts_val, 'symbol': sym, 'close': fast_1m['close'][idx_1m],
416
+ 'real_titan': 0.6, 'oracle_conf': 0.5, 'sniper_score': 0.5,
417
+ 'risk_hydra_crash': 0.0, 'time_hydra_crash': 0,
418
+ 'risk_legacy_v2': 0.0, 'time_legacy_panic': 0,
419
+ 'signal_type': 'BREAKOUT', 'l1_score': 50.0
420
+ })
421
+
422
+ # --- EXECUTE BATCHES IF FULL ---
423
+ if len(hydra_batch_X) >= BATCH_SIZE:
424
+ # 1. Oracle
425
+ if oracle_batch_X:
426
+ try:
427
+ o_preds = oracle_dir_model.predict(np.array(oracle_batch_X))
428
+ start_i = current_res_idx - len(oracle_batch_X) + 1
429
+ for i, p in enumerate(o_preds):
430
+ val = float(p[0]) if isinstance(p, (list, np.ndarray)) else float(p)
431
+ if val < 0.5: val = 1 - val
432
+ temp_oracle_results[start_i + i] = val
433
+ except: pass
434
+ oracle_batch_X = []
435
+
436
+ # 2. Sniper
437
+ if sniper_batch_X:
438
+ try:
439
+ s_X = np.array(sniper_batch_X)
440
+ # Average of models
441
+ avg_preds = np.zeros(len(s_X))
442
+ for m in sniper_models:
443
+ avg_preds += m.predict(s_X)
444
+ avg_preds /= len(sniper_models)
445
+
446
+ start_i = current_res_idx - len(sniper_batch_X) + 1
447
+ for i, p in enumerate(avg_preds):
448
+ temp_sniper_results[start_i + i] = float(p)
449
+ except: pass
450
+ sniper_batch_X = []
451
+
452
+ # 3. Hydra
453
+ if hydra_batch_X:
454
  big_X = np.vstack(hydra_batch_X)
455
  try:
 
456
  preds = hydra_models['crash'].predict_proba(big_X)[:, 1]
 
457
  for b_i, res_idx in enumerate(hydra_batch_indices):
458
  p_slice = preds[b_i*240 : (b_i+1)*240]
459
  max_p = np.max(p_slice)
460
  c_idx = np.where(p_slice > 0.6)[0]
461
+ c_time = int(fast_1m['timestamp'][fast_1m['timestamp'].searchsorted(ai_results[res_idx]['timestamp']) + 1 + c_idx[0]]) if len(c_idx) > 0 else 0
462
  temp_hydra_results[res_idx] = (max_p, c_time)
463
  except: pass
464
  hydra_batch_X = []
465
  hydra_batch_indices = []
466
 
467
+ # --- PROCESS LEFTOVERS ---
468
+ if oracle_batch_X:
469
+ try:
470
+ o_preds = oracle_dir_model.predict(np.array(oracle_batch_X))
471
+ start_i = len(ai_results) - len(oracle_batch_X)
472
+ for i, p in enumerate(o_preds):
473
+ val = float(p[0]) if isinstance(p, (list, np.ndarray)) else float(p)
474
+ if val < 0.5: val = 1 - val
475
+ temp_oracle_results[start_i + i] = val
476
+ except: pass
477
 
478
+ if sniper_batch_X:
479
+ try:
480
+ s_X = np.array(sniper_batch_X)
481
+ avg_preds = np.zeros(len(s_X))
482
+ for m in sniper_models: avg_preds += m.predict(s_X)
483
+ avg_preds /= len(sniper_models)
484
+ start_i = len(ai_results) - len(sniper_batch_X)
485
+ for i, p in enumerate(avg_preds):
486
+ temp_sniper_results[start_i + i] = float(p)
487
+ except: pass
488
 
 
489
  if hydra_batch_X:
490
  big_X = np.vstack(hydra_batch_X)
491
  try:
 
498
  temp_hydra_results[res_idx] = (max_p, c_time)
499
  except: pass
500
 
501
+ # --- Legacy V2 Vectorized Fill ---
502
+ if legacy_v2:
503
+ # Just map pre-calculated global values
504
+ for idx, res in enumerate(ai_results):
505
+ ts = res['timestamp']
506
+ idx_1m = np.searchsorted(fast_1m['timestamp'], ts)
507
+ start = idx_1m + 1
508
+ # Safety check
509
+ if start < len(global_v2_probs) - 240:
510
+ probs_slice = global_v2_probs[start:start+240]
511
+ max_p = np.max(probs_slice)
512
+ p_idx = np.where(probs_slice > 0.8)[0]
513
+ p_time = int(fast_1m['timestamp'][start + p_idx[0]]) if len(p_idx) > 0 else 0
514
+ ai_results[idx]['risk_legacy_v2'] = max_p
515
+ ai_results[idx]['time_legacy_panic'] = p_time
516
+
517
+ # --- FILL ALL RESULTS ---
518
+ for i in range(len(ai_results)):
519
+ if i in temp_oracle_results: ai_results[i]['oracle_conf'] = temp_oracle_results[i]
520
+ if i in temp_sniper_results: ai_results[i]['sniper_score'] = temp_sniper_results[i]
521
+ if i in temp_hydra_results:
522
+ ai_results[i]['risk_hydra_crash'] = temp_hydra_results[i][0]
523
+ ai_results[i]['time_hydra_crash'] = temp_hydra_results[i][1]
524
 
525
  dt = time.time() - t0
526
  if ai_results:
 
566
  global_df.sort_values('timestamp', inplace=True)
567
 
568
  # 🚀 CONVERT TO NUMPY ARRAYS FOR BLAZING SPEED 🚀
 
569
  arr_ts = global_df['timestamp'].values
570
  arr_close = global_df['close'].values.astype(np.float64)
571
  arr_symbol = global_df['symbol'].values
 
575
  arr_hydra_time = global_df['time_hydra_crash'].values.astype(np.int64)
576
  arr_titan = global_df['real_titan'].values.astype(np.float64)
577
 
 
578
  unique_syms = np.unique(arr_symbol)
579
  sym_map = {s: i for i, s in enumerate(unique_syms)}
580
  arr_sym_int = np.array([sym_map[s] for s in arr_symbol], dtype=np.int32)
 
587
  start_time = time.time()
588
 
589
  for idx, config in enumerate(combinations_batch):
 
590
  if idx > 0 and idx % 10 == 0:
591
  elapsed = time.time() - start_time
592
  avg_time = elapsed / idx
 
594
  sys.stdout.write(f"\r ⚙️ Progress: {idx}/{total_combos} ({idx/total_combos:.1%}) | ETA: {rem_time:.0f}s")
595
  sys.stdout.flush()
596
 
 
597
  wallet_bal = initial_capital
598
  wallet_alloc = 0.0
 
599
  positions = {}
600
+ trades_log = []
601
 
602
  oracle_thresh = config.get('oracle_thresh', 0.6)
603
  sniper_thresh = config.get('sniper_thresh', 0.4)
604
  hydra_thresh = config['hydra_thresh']
605
 
606
+ # Vectorized Mask
 
607
  mask_buy = (arr_oracle >= oracle_thresh) & (arr_sniper >= sniper_thresh)
608
 
609
  peak_bal = initial_capital
610
  max_dd = 0.0
 
 
 
 
 
 
 
 
611
 
612
+ # Optimized Loop
 
613
  for i in range(total_len):
614
  ts = arr_ts[i]
615
  sym_id = arr_sym_int[i]
616
  price = arr_close[i]
617
 
618
+ # Check Exits
619
  if sym_id in positions:
620
+ pos = positions[sym_id]
621
  entry = pos[0]
622
 
 
623
  h_risk = pos[2]
624
  h_time = pos[3]
625
  is_crash = (h_risk > hydra_thresh) and (h_time > 0) and (ts >= h_time)
 
632
  trades_log.append((pnl, pos[4]))
633
  del positions[sym_id]
634
 
 
635
  tot = wallet_bal + wallet_alloc
636
  if tot > peak_bal: peak_bal = tot
637
  else:
 
639
  if dd > max_dd: max_dd = dd
640
 
641
  # Check Entries
 
642
  if len(positions) < max_slots:
643
  if mask_buy[i]:
644
  if sym_id not in positions:
645
  if wallet_bal >= 10.0:
646
  cons_score = (arr_titan[i] + arr_oracle[i] + arr_sniper[i]) / 3.0
 
647
  size = 10.0
648
  positions[sym_id] = [price, size, arr_hydra_risk[i], arr_hydra_time[i], cons_score]
649
  wallet_bal -= size
650
  wallet_alloc += size
651
 
652
+ # Stats
653
  final_bal = wallet_bal + wallet_alloc
654
  net_profit = final_bal - initial_capital
655
  total_t = len(trades_log)
 
702
  'high_consensus_avg_pnl': hc_avg_pnl
703
  })
704
 
705
+ print("")
706
  return results
707
 
708
  async def run_optimization(self, target_regime="RANGE"):
 
765
  hub = AdaptiveHub(r2); await hub.initialize()
766
  optimizer = HeavyDutyBacktester(dm, proc)
767
 
768
+ # ✅ Updated Scenarios List
769
  scenarios = [
770
  {"regime": "BULL", "start": "2024-01-01", "end": "2024-03-30"},
771
+ {"regime": "BEAR", "start": "2023-08-01", "end": "2023-09-15"},
772
+ {"regime": "DEAD", "start": "2023-06-01", "end": "2023-08-01"},
773
+ {"regime": "RANGE", "start": "2024-07-01", "end": "2024-09-30"}
774
  ]
775
 
776
  for scen in scenarios: