Riy777 commited on
Commit
d2882ac
Β·
verified Β·
1 Parent(s): fe3efe4

Update backtest_engine.py

Browse files
Files changed (1) hide show
  1. backtest_engine.py +89 -40
backtest_engine.py CHANGED
@@ -1,5 +1,5 @@
1
  # ============================================================
2
- # πŸ§ͺ backtest_engine.py (V113.0 - GEM-Architect: Global Pre-Inference)
3
  # ============================================================
4
 
5
  import asyncio
@@ -23,7 +23,6 @@ try:
23
  from r2 import R2Service
24
  import ccxt.async_support as ccxt
25
  import xgboost as xgb
26
- import lightgbm as lgb
27
  except ImportError:
28
  pass
29
 
@@ -34,28 +33,26 @@ class HeavyDutyBacktester:
34
  def __init__(self, data_manager, processor):
35
  self.dm = data_manager
36
  self.proc = processor
37
- self.GRID_DENSITY = 5
 
 
 
 
 
 
38
  self.INITIAL_CAPITAL = 10.0
39
  self.TRADING_FEES = 0.001
40
  self.MAX_SLOTS = 4
41
 
42
  self.TARGET_COINS = [
43
- 'SOL/USDT', 'XRP/USDT', 'DOGE/USDT', 'ADA/USDT', 'AVAX/USDT', 'LINK/USDT',
44
- 'TON/USDT', 'INJ/USDT', 'APT/USDT', 'OP/USDT', 'ARB/USDT', 'SUI/USDT',
45
- 'SEI/USDT', 'TIA/USDT', 'ETH/USDT', 'NEAR/USDT', 'RUNE/USDT', 'PYTH/USDT',
46
- 'WIF/USDT', 'PEPE/USDT', 'SHIB/USDT', 'TRX/USDT', 'DOT/USDT', 'UNI/USDT',
47
- 'ONDO/USDT', 'ENA/USDT', 'HBAR/USDT', 'XLM/USDT', 'TAO/USDT', 'BTC/USDT',
48
- 'ZRO/USDT', 'KCS/USDT', 'ICP/USDT', 'SAND/USDT', 'AXS/USDT', 'APE/USDT',
49
- 'GMT/USDT', 'CHZ/USDT', 'CFX/USDT', 'LDO/USDT', 'FET/USDT', 'JTO/USDT',
50
- 'STRK/USDT', 'BLUR/USDT', 'ALT/USDT', 'JUP/USDT', 'PENDLE/USDT', 'ETHFI/USDT',
51
- 'MEME/USDT', 'ATOM/USDT'
52
  ]
53
 
54
  self.force_start_date = None
55
  self.force_end_date = None
56
 
57
  if not os.path.exists(CACHE_DIR): os.makedirs(CACHE_DIR)
58
- print(f"πŸ§ͺ [Backtest V113.0] Pre-Inference Velocity Mode (Target: 60s).")
59
 
60
  def set_date_range(self, start_str, end_str):
61
  self.force_start_date = start_str
@@ -237,7 +234,6 @@ class HeavyDutyBacktester:
237
  legacy_v2 = getattr(self.proc.guardian_legacy, 'model_v2', None)
238
 
239
  # 5. πŸ”₯ PRE-CALCULATE LEGACY V2 (GLOBAL) πŸ”₯
240
- # V2 depends only on structure, not entry price. We can predict for ALL rows at once.
241
  global_v2_probs = np.zeros(len(fast_1m['close']))
242
 
243
  if legacy_v2:
@@ -284,7 +280,6 @@ class HeavyDutyBacktester:
284
  except Exception as e: print(f"V2 Error: {e}")
285
 
286
  # 6. πŸ”₯ PRE-ASSEMBLE HYDRA STATIC (GLOBAL) πŸ”₯
287
- # Hydra needs PnL (dynamic), but 90% features are static.
288
  global_hydra_static = None
289
  if hydra_models:
290
  print(f" πŸš€ Pre-assembling Hydra features...", flush=True)
@@ -298,8 +293,6 @@ class HeavyDutyBacktester:
298
  h_atr = fast_1m['atr']
299
  h_close = fast_1m['close']
300
 
301
- # We store these separate to combine inside loop efficiently
302
- # [rsi1, rsi5, rsi15, bb, vol, atr, close]
303
  global_hydra_static = np.column_stack([h_rsi_1m, h_rsi_5m, h_rsi_15m, h_bb, h_vol, h_atr, h_close])
304
  except: pass
305
 
@@ -386,7 +379,7 @@ class HeavyDutyBacktester:
386
  max_hydra_crash = 0.0; hydra_crash_time = 0
387
  if hydra_models and global_hydra_static is not None:
388
  # Slice Static Feats
389
- sl_static = global_hydra_static[start_idx:end_idx] # [rsi1, rsi5, rsi15, bb, vol, atr, close]
390
 
391
  entry_price = fast_1m['close'][idx_1m]
392
  sl_close = sl_static[:, 6]
@@ -405,13 +398,6 @@ class HeavyDutyBacktester:
405
 
406
  sl_atr_pct = sl_atr / sl_close
407
 
408
- # Map to Hydra Cols Order (Hardcoded for max speed)
409
- # Cols: rsi_1m, rsi_5m, rsi_15m, bb_width, rel_vol, dist_ema20_1h, atr_pct, norm_pnl_r, max_pnl_r, dists..., time, entry, oracle, l2, target
410
-
411
- # Re-assemble only what is needed
412
- # (Static 0-4) + (Zeros) + (Dynamic) + (Constants)
413
-
414
- # Create arrays for constants
415
  zeros = np.zeros(240)
416
  oracle_arr = np.full(240, oracle_conf)
417
  l2_arr = np.full(240, 0.7)
@@ -437,7 +423,7 @@ class HeavyDutyBacktester:
437
 
438
  ai_results.append({
439
  'timestamp': ts_val, 'symbol': sym, 'close': entry_price,
440
- 'real_titan': 0.6,
441
  'oracle_conf': oracle_conf,
442
  'sniper_score': sniper_score,
443
  'risk_hydra_crash': max_hydra_crash,
@@ -459,7 +445,7 @@ class HeavyDutyBacktester:
459
  gc.collect()
460
 
461
  # ==============================================================
462
- # PHASE 1 & 2 (Unchanged - Standard Optimization Logic)
463
  # ==============================================================
464
  async def generate_truth_data(self):
465
  if self.force_start_date and self.force_end_date:
@@ -493,8 +479,14 @@ class HeavyDutyBacktester:
493
 
494
  for config in combinations_batch:
495
  wallet = { "balance": initial_capital, "allocated": 0.0, "positions": {}, "trades_history": [] }
496
- w_titan = config['w_titan']; oracle_thresh = config.get('oracle_thresh', 0.6)
497
- sniper_thresh = config.get('sniper_thresh', 0.4); hydra_thresh = config['hydra_thresh']
 
 
 
 
 
 
498
  peak_balance = initial_capital; max_drawdown = 0.0
499
 
500
  for ts, group in grouped_by_time:
@@ -511,8 +503,12 @@ class HeavyDutyBacktester:
511
  if is_crash or pnl > 0.04 or pnl < -0.02:
512
  wallet['balance'] += pos['size'] * (1 + pnl - (fees_pct*2))
513
  wallet['allocated'] -= pos['size']
 
 
 
 
 
514
  del wallet['positions'][sym]
515
- wallet['trades_history'].append({'pnl': pnl})
516
 
517
  total_eq = wallet['balance'] + wallet['allocated']
518
  if total_eq > peak_balance: peak_balance = total_eq
@@ -522,15 +518,22 @@ class HeavyDutyBacktester:
522
  if len(wallet['positions']) < max_slots:
523
  for _, row in group.iterrows():
524
  if row['symbol'] in wallet['positions']: continue
 
 
525
  if row['oracle_conf'] < oracle_thresh: continue
526
  if row['sniper_score'] < sniper_thresh: continue
527
 
 
 
 
 
528
  size = 10.0
529
  if wallet['balance'] >= size:
530
  wallet['positions'][row['symbol']] = {
531
  'entry': row['close'], 'size': size,
532
  'risk_hydra_crash': row['risk_hydra_crash'],
533
- 'time_hydra_crash': row['time_hydra_crash']
 
534
  }
535
  wallet['balance'] -= size
536
  wallet['allocated'] += size
@@ -545,6 +548,7 @@ class HeavyDutyBacktester:
545
  max_win = max([t['pnl'] for t in trades]) if trades else 0
546
  max_loss = min([t['pnl'] for t in trades]) if trades else 0
547
 
 
548
  max_win_streak = 0; max_loss_streak = 0; curr_w = 0; curr_l = 0
549
  for t in trades:
550
  if t['pnl'] > 0:
@@ -554,32 +558,68 @@ class HeavyDutyBacktester:
554
  curr_l += 1; curr_w = 0
555
  if curr_l > max_loss_streak: max_loss_streak = curr_l
556
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
557
  results.append({
558
  'config': config, 'final_balance': final_bal, 'net_profit': net_profit,
559
  'total_trades': total_t, 'win_count': win_count, 'loss_count': loss_count,
560
  'win_rate': win_rate, 'max_single_win': max_win, 'max_single_loss': max_loss,
561
- 'max_drawdown': max_drawdown * 100
 
 
 
 
 
 
 
562
  })
563
 
564
  return results
565
 
566
  async def run_optimization(self, target_regime="RANGE"):
567
  await self.generate_truth_data()
568
- oracle_range = [0.5, 0.6, 0.7]
569
- sniper_range = [0.4, 0.5, 0.6]
570
- hydra_range = [0.75, 0.85, 0.95]
 
 
 
 
 
 
 
 
 
571
  combinations = []
572
- for o, s, h in itertools.product(oracle_range, sniper_range, hydra_range):
 
573
  combinations.append({
574
- 'w_titan': 0.5, 'w_struct': 0.3, 'thresh': 0.5,
575
- 'oracle_thresh': o, 'sniper_thresh': s, 'hydra_thresh': h,
 
 
 
 
576
  'legacy_thresh': 0.95
577
  })
578
 
579
  current_period_files = [os.path.join(CACHE_DIR, f) for f in os.listdir(CACHE_DIR) if f.endswith('_scores.pkl')]
580
  if not current_period_files: return None, None
581
 
582
- print(f"\n🧩 [Phase 2] Optimizing {len(combinations)} Configs (Full Stack) for {target_regime}...")
583
  best_res = self._worker_optimize(combinations, current_period_files, self.INITIAL_CAPITAL, self.TRADING_FEES, self.MAX_SLOTS)
584
  if not best_res: return None, None
585
  best = sorted(best_res, key=lambda x: x['final_balance'], reverse=True)[0]
@@ -595,13 +635,19 @@ class HeavyDutyBacktester:
595
  print(f" ❌ Losing Trades: {best['loss_count']}")
596
  print(f" πŸ“ˆ Win Rate: {best['win_rate']:.1f}%")
597
  print("-" * 60)
 
 
 
 
 
598
  print(f" 🟒 Max Single Win: ${best['max_single_win']:.2f}")
599
  print(f" πŸ”΄ Max Single Loss: ${best['max_single_loss']:.2f}")
600
  print(f" πŸ”₯ Max Win Streak: {best['max_win_streak']} trades")
601
  print(f" 🧊 Max Loss Streak: {best['max_loss_streak']} trades")
602
  print(f" πŸ“‰ Max Drawdown: {best['max_drawdown']:.1f}%")
603
  print("-" * 60)
604
- print(f" βš™οΈ Oracle={best['config']['oracle_thresh']} | Sniper={best['config']['sniper_thresh']} | Hydra={best['config']['hydra_thresh']}")
 
605
  print("="*60)
606
  return best['config'], best
607
 
@@ -617,6 +663,9 @@ async def run_strategic_optimization_task():
617
  hub = AdaptiveHub(r2); await hub.initialize()
618
  optimizer = HeavyDutyBacktester(dm, proc)
619
 
 
 
 
620
  scenarios = [
621
  {"regime": "BULL", "start": "2024-01-01", "end": "2024-03-30"},
622
  {"regime": "BEAR", "start": "2023-08-01", "end": "2023-09-15"},
 
1
  # ============================================================
2
+ # πŸ§ͺ backtest_engine.py (V115.0 - GEM-Architect: Full Grid Density)
3
  # ============================================================
4
 
5
  import asyncio
 
23
  from r2 import R2Service
24
  import ccxt.async_support as ccxt
25
  import xgboost as xgb
 
26
  except ImportError:
27
  pass
28
 
 
33
  def __init__(self, data_manager, processor):
34
  self.dm = data_manager
35
  self.proc = processor
36
+
37
+ # πŸŽ›οΈ GRID DENSITY CONTROL
38
+ # 3 = Low (243 Scenarios) - Fast
39
+ # 4 = Med (1024 Scenarios) - Balanced
40
+ # 5 = High (3125 Scenarios) - Deep Search
41
+ self.GRID_DENSITY = 3
42
+
43
  self.INITIAL_CAPITAL = 10.0
44
  self.TRADING_FEES = 0.001
45
  self.MAX_SLOTS = 4
46
 
47
  self.TARGET_COINS = [
48
+ 'SOL/USDT', 'XRP/USDT', 'DOGE/USDT'
 
 
 
 
 
 
 
 
49
  ]
50
 
51
  self.force_start_date = None
52
  self.force_end_date = None
53
 
54
  if not os.path.exists(CACHE_DIR): os.makedirs(CACHE_DIR)
55
+ print(f"πŸ§ͺ [Backtest V115.0] Grid Density: {self.GRID_DENSITY} | Full Stack Optimization.")
56
 
57
  def set_date_range(self, start_str, end_str):
58
  self.force_start_date = start_str
 
234
  legacy_v2 = getattr(self.proc.guardian_legacy, 'model_v2', None)
235
 
236
  # 5. πŸ”₯ PRE-CALCULATE LEGACY V2 (GLOBAL) πŸ”₯
 
237
  global_v2_probs = np.zeros(len(fast_1m['close']))
238
 
239
  if legacy_v2:
 
280
  except Exception as e: print(f"V2 Error: {e}")
281
 
282
  # 6. πŸ”₯ PRE-ASSEMBLE HYDRA STATIC (GLOBAL) πŸ”₯
 
283
  global_hydra_static = None
284
  if hydra_models:
285
  print(f" πŸš€ Pre-assembling Hydra features...", flush=True)
 
293
  h_atr = fast_1m['atr']
294
  h_close = fast_1m['close']
295
 
 
 
296
  global_hydra_static = np.column_stack([h_rsi_1m, h_rsi_5m, h_rsi_15m, h_bb, h_vol, h_atr, h_close])
297
  except: pass
298
 
 
379
  max_hydra_crash = 0.0; hydra_crash_time = 0
380
  if hydra_models and global_hydra_static is not None:
381
  # Slice Static Feats
382
+ sl_static = global_hydra_static[start_idx:end_idx]
383
 
384
  entry_price = fast_1m['close'][idx_1m]
385
  sl_close = sl_static[:, 6]
 
398
 
399
  sl_atr_pct = sl_atr / sl_close
400
 
 
 
 
 
 
 
 
401
  zeros = np.zeros(240)
402
  oracle_arr = np.full(240, oracle_conf)
403
  l2_arr = np.full(240, 0.7)
 
423
 
424
  ai_results.append({
425
  'timestamp': ts_val, 'symbol': sym, 'close': entry_price,
426
+ 'real_titan': 0.6, # Placeholder for real Titan score if available
427
  'oracle_conf': oracle_conf,
428
  'sniper_score': sniper_score,
429
  'risk_hydra_crash': max_hydra_crash,
 
445
  gc.collect()
446
 
447
  # ==============================================================
448
+ # PHASE 1 & 2 (Enhanced with Consensus Analytics)
449
  # ==============================================================
450
  async def generate_truth_data(self):
451
  if self.force_start_date and self.force_end_date:
 
479
 
480
  for config in combinations_batch:
481
  wallet = { "balance": initial_capital, "allocated": 0.0, "positions": {}, "trades_history": [] }
482
+
483
+ # Param Extraction
484
+ oracle_thresh = config.get('oracle_thresh', 0.6)
485
+ sniper_thresh = config.get('sniper_thresh', 0.4)
486
+ hydra_thresh = config['hydra_thresh']
487
+ # Titan & Pattern weights are in config but not used for hard filtering here,
488
+ # they are optimized for the DNA output.
489
+
490
  peak_balance = initial_capital; max_drawdown = 0.0
491
 
492
  for ts, group in grouped_by_time:
 
503
  if is_crash or pnl > 0.04 or pnl < -0.02:
504
  wallet['balance'] += pos['size'] * (1 + pnl - (fees_pct*2))
505
  wallet['allocated'] -= pos['size']
506
+ # Add consensus data to history
507
+ wallet['trades_history'].append({
508
+ 'pnl': pnl,
509
+ 'consensus_score': pos['consensus_score']
510
+ })
511
  del wallet['positions'][sym]
 
512
 
513
  total_eq = wallet['balance'] + wallet['allocated']
514
  if total_eq > peak_balance: peak_balance = total_eq
 
518
  if len(wallet['positions']) < max_slots:
519
  for _, row in group.iterrows():
520
  if row['symbol'] in wallet['positions']: continue
521
+
522
+ # Hard Filters
523
  if row['oracle_conf'] < oracle_thresh: continue
524
  if row['sniper_score'] < sniper_thresh: continue
525
 
526
+ # Consensus Calculation (Normalized)
527
+ # Titan (default 0.6) + Oracle + Sniper
528
+ cons_score = (row['real_titan'] + row['oracle_conf'] + row['sniper_score']) / 3.0
529
+
530
  size = 10.0
531
  if wallet['balance'] >= size:
532
  wallet['positions'][row['symbol']] = {
533
  'entry': row['close'], 'size': size,
534
  'risk_hydra_crash': row['risk_hydra_crash'],
535
+ 'time_hydra_crash': row['time_hydra_crash'],
536
+ 'consensus_score': cons_score
537
  }
538
  wallet['balance'] -= size
539
  wallet['allocated'] += size
 
548
  max_win = max([t['pnl'] for t in trades]) if trades else 0
549
  max_loss = min([t['pnl'] for t in trades]) if trades else 0
550
 
551
+ # 1. Fix: Calculate Streaks
552
  max_win_streak = 0; max_loss_streak = 0; curr_w = 0; curr_l = 0
553
  for t in trades:
554
  if t['pnl'] > 0:
 
558
  curr_l += 1; curr_w = 0
559
  if curr_l > max_loss_streak: max_loss_streak = curr_l
560
 
561
+ # 2. Fix: Consensus Analytics
562
+ high_cons_trades = [t for t in trades if t['consensus_score'] > 0.65]
563
+ low_cons_trades = [t for t in trades if t['consensus_score'] <= 0.65]
564
+
565
+ hc_count = len(high_cons_trades)
566
+ hc_wins = len([t for t in high_cons_trades if t['pnl'] > 0])
567
+ hc_win_rate = (hc_wins/hc_count*100) if hc_count > 0 else 0
568
+ hc_avg_pnl = (sum([t['pnl'] for t in high_cons_trades]) / hc_count * 100) if hc_count > 0 else 0
569
+
570
+ lc_count = len(low_cons_trades)
571
+ lc_wins = len([t for t in low_cons_trades if t['pnl'] > 0])
572
+ lc_win_rate = (lc_wins/lc_count*100) if lc_count > 0 else 0
573
+
574
+ agreement_rate = (hc_count / total_t * 100) if total_t > 0 else 0.0
575
+
576
  results.append({
577
  'config': config, 'final_balance': final_bal, 'net_profit': net_profit,
578
  'total_trades': total_t, 'win_count': win_count, 'loss_count': loss_count,
579
  'win_rate': win_rate, 'max_single_win': max_win, 'max_single_loss': max_loss,
580
+ 'max_drawdown': max_drawdown * 100,
581
+ # New Fields
582
+ 'max_win_streak': max_win_streak,
583
+ 'max_loss_streak': max_loss_streak,
584
+ 'consensus_agreement_rate': agreement_rate,
585
+ 'high_consensus_win_rate': hc_win_rate,
586
+ 'low_consensus_win_rate': lc_win_rate,
587
+ 'high_consensus_avg_pnl': hc_avg_pnl
588
  })
589
 
590
  return results
591
 
592
  async def run_optimization(self, target_regime="RANGE"):
593
  await self.generate_truth_data()
594
+
595
+ # πŸ”₯ Dynamic Ranges based on GRID_DENSITY
596
+ density = self.GRID_DENSITY
597
+
598
+ oracle_range = np.linspace(0.5, 0.8, density).tolist()
599
+ sniper_range = np.linspace(0.4, 0.7, density).tolist()
600
+ hydra_range = np.linspace(0.75, 0.95, density).tolist()
601
+
602
+ # New Params (Titan & Pattern)
603
+ titan_range = np.linspace(0.4, 0.7, density).tolist()
604
+ pattern_range = np.linspace(0.2, 0.5, density).tolist()
605
+
606
  combinations = []
607
+ # Full Stack Loop
608
+ for o, s, h, wt, wp in itertools.product(oracle_range, sniper_range, hydra_range, titan_range, pattern_range):
609
  combinations.append({
610
+ 'w_titan': wt,
611
+ 'w_struct': wp,
612
+ 'thresh': 0.5,
613
+ 'oracle_thresh': o,
614
+ 'sniper_thresh': s,
615
+ 'hydra_thresh': h,
616
  'legacy_thresh': 0.95
617
  })
618
 
619
  current_period_files = [os.path.join(CACHE_DIR, f) for f in os.listdir(CACHE_DIR) if f.endswith('_scores.pkl')]
620
  if not current_period_files: return None, None
621
 
622
+ print(f"\n🧩 [Phase 2] Optimizing {len(combinations)} Configs (Full Stack | Density {density}) for {target_regime}...")
623
  best_res = self._worker_optimize(combinations, current_period_files, self.INITIAL_CAPITAL, self.TRADING_FEES, self.MAX_SLOTS)
624
  if not best_res: return None, None
625
  best = sorted(best_res, key=lambda x: x['final_balance'], reverse=True)[0]
 
635
  print(f" ❌ Losing Trades: {best['loss_count']}")
636
  print(f" πŸ“ˆ Win Rate: {best['win_rate']:.1f}%")
637
  print("-" * 60)
638
+ print(f" 🧠 CONSENSUS ANALYTICS:")
639
+ print(f" 🀝 Model Agreement Rate: {best['consensus_agreement_rate']:.1f}% (of all trades)")
640
+ print(f" 🌟 High-Consensus Win Rate: {best['high_consensus_win_rate']:.1f}%")
641
+ print(f" πŸ’Ž High-Consensus Avg PnL: {best['high_consensus_avg_pnl']:.2f}%")
642
+ print("-" * 60)
643
  print(f" 🟒 Max Single Win: ${best['max_single_win']:.2f}")
644
  print(f" πŸ”΄ Max Single Loss: ${best['max_single_loss']:.2f}")
645
  print(f" πŸ”₯ Max Win Streak: {best['max_win_streak']} trades")
646
  print(f" 🧊 Max Loss Streak: {best['max_loss_streak']} trades")
647
  print(f" πŸ“‰ Max Drawdown: {best['max_drawdown']:.1f}%")
648
  print("-" * 60)
649
+ print(f" βš™οΈ Oracle={best['config']['oracle_thresh']:.2f} | Sniper={best['config']['sniper_thresh']:.2f} | Hydra={best['config']['hydra_thresh']:.2f}")
650
+ print(f" βš–οΈ Weights: Titan={best['config']['w_titan']:.2f} | Patterns={best['config']['w_struct']:.2f}")
651
  print("="*60)
652
  return best['config'], best
653
 
 
663
  hub = AdaptiveHub(r2); await hub.initialize()
664
  optimizer = HeavyDutyBacktester(dm, proc)
665
 
666
+ # ⚑ ADJUST DENSITY HERE IF NEEDED
667
+ # optimizer.GRID_DENSITY = 3 (Default)
668
+
669
  scenarios = [
670
  {"regime": "BULL", "start": "2024-01-01", "end": "2024-03-30"},
671
  {"regime": "BEAR", "start": "2023-08-01", "end": "2023-09-15"},