Riy777 commited on
Commit
5b1d2b3
Β·
verified Β·
1 Parent(s): 8376ab1

Update backtest_engine.py

Browse files
Files changed (1) hide show
  1. backtest_engine.py +78 -27
backtest_engine.py CHANGED
@@ -399,7 +399,9 @@ 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
  @staticmethod
404
  def _worker_optimize(combinations_batch, scores_files, initial_capital, fees_pct, max_slots):
405
  print(f" ⏳ [System] Loading {len(scores_files)} datasets...", flush=True)
@@ -410,7 +412,7 @@ class HeavyDutyBacktester:
410
  if not data: return []
411
  df = pd.concat(data).sort_values('timestamp')
412
 
413
- # Arrays
414
  ts = df['timestamp'].values; close = df['close'].values.astype(float)
415
  sym = df['symbol'].values; sym_map = {s:i for i,s in enumerate(np.unique(sym))}
416
  sym_id = np.array([sym_map[s] for s in sym])
@@ -424,65 +426,113 @@ class HeavyDutyBacktester:
424
 
425
  res = []
426
  for cfg in combinations_batch:
427
- pos = {}; log = []
428
  bal = initial_capital; alloc = 0.0
429
 
 
430
  mask = (l1 >= cfg['l1_thresh']) & (oracle >= cfg['oracle_thresh']) & (sniper >= cfg['sniper_thresh'])
431
 
432
  for i in range(N):
433
  s = sym_id[i]; p = close[i]
434
 
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
 
 
441
  if crash or pnl > 0.04 or pnl < -0.02:
442
- bal += pos[s][2] * (1 + pnl - fees_pct*2)
 
 
 
443
  alloc -= pos[s][2]
444
- log.append(pnl)
 
 
 
 
445
  del pos[s]
446
 
447
- # Entry
448
  if len(pos) < max_slots and mask[i]:
449
  if s not in pos and bal >= 5.0:
450
- size = min(10.0, bal * 0.98)
451
- pos[s] = (p, hydra[i], size)
 
452
  bal -= size; alloc += size
453
 
454
- # Metrics
455
- profit = (bal + alloc) - initial_capital
456
- wins = sum(1 for x in log if x > 0)
457
- tot = len(log)
 
 
 
 
 
 
 
 
 
 
 
 
458
  res.append({
459
- 'config': cfg, 'net_profit': profit, 'total_trades': tot,
460
- 'win_rate': (wins/tot*100) if tot else 0, 'max_drawdown': 0
 
 
 
 
 
 
 
461
  })
 
462
  return res
463
 
 
 
 
464
  async def run_optimization(self, target_regime="RANGE"):
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}")
@@ -490,17 +540,18 @@ class HeavyDutyBacktester:
490
  print("-" * 60)
491
  print(f" πŸ“Š Total Trades: {best['total_trades']}")
492
  print(f" πŸ“ˆ Win Rate: {best['win_rate']:.1f}%")
493
- print(f" πŸ“‰ Max Drawdown: {best['max_drawdown']:.1f}%")
494
  print("-" * 60)
495
  print(f" 🧠 CONSENSUS ANALYTICS:")
496
- print(f" 🀝 Model Agreement Rate: {best['consensus_agreement_rate']:.1f}%")
497
  print(f" 🌟 High-Consensus Win Rate: {best['high_consensus_win_rate']:.1f}%")
498
  print(f" πŸ’Ž High-Consensus Avg PnL: {best['high_consensus_avg_pnl']:.2f}%")
499
  print("-" * 60)
500
  print(f" βš™οΈ Oracle={best['config']['oracle_thresh']:.2f} | Sniper={best['config']['sniper_thresh']:.2f} | Hydra={best['config']['hydra_thresh']:.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)
 
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
  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])
 
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}]:")
538
  print(f" πŸ’° Final Balance: ${best['final_balance']:,.2f}")
 
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)