Riy777 commited on
Commit
6ab19fc
·
verified ·
1 Parent(s): d2882ac

Update backtest_engine.py

Browse files
Files changed (1) hide show
  1. backtest_engine.py +168 -105
backtest_engine.py CHANGED
@@ -1,5 +1,5 @@
1
  # ============================================================
2
- # 🧪 backtest_engine.py (V115.0 - GEM-Architect: Full Grid Density)
3
  # ============================================================
4
 
5
  import asyncio
@@ -10,6 +10,7 @@ import time
10
  import logging
11
  import itertools
12
  import os
 
13
  import gc
14
  import sys
15
  import traceback
@@ -35,15 +36,14 @@ class HeavyDutyBacktester:
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
  ]
@@ -51,15 +51,25 @@ class HeavyDutyBacktester:
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
59
  self.force_end_date = end_str
60
 
61
  # ==============================================================
62
- # ⚡ FAST DATA DOWNLOADER
63
  # ==============================================================
64
  async def _fetch_all_data_fast(self, sym, start_ms, end_ms):
65
  print(f" ⚡ [Network] Downloading {sym}...", flush=True)
@@ -70,6 +80,7 @@ class HeavyDutyBacktester:
70
  while current < end_ms:
71
  tasks.append(current)
72
  current += duration_per_batch
 
73
  all_candles = []
74
  sem = asyncio.Semaphore(10)
75
 
@@ -90,6 +101,8 @@ class HeavyDutyBacktester:
90
  if res: all_candles.extend(res)
91
 
92
  if not all_candles: return None
 
 
93
  filtered = [c for c in all_candles if c[0] >= start_ms and c[0] <= end_ms]
94
  seen = set(); unique_candles = []
95
  for c in filtered:
@@ -101,21 +114,28 @@ class HeavyDutyBacktester:
101
  return unique_candles
102
 
103
  # ==============================================================
104
- # 🏎️ VECTORIZED INDICATORS
105
  # ==============================================================
106
  def _calculate_indicators_vectorized(self, df, timeframe='1m'):
 
 
 
 
 
107
  df['close'] = df['close'].astype(float)
108
  df['high'] = df['high'].astype(float)
109
  df['low'] = df['low'].astype(float)
110
  df['volume'] = df['volume'].astype(float)
111
  df['open'] = df['open'].astype(float)
112
 
 
113
  df['rsi'] = ta.rsi(df['close'], length=14)
114
  df['ema20'] = ta.ema(df['close'], length=20)
115
  df['ema50'] = ta.ema(df['close'], length=50)
116
  df['atr'] = ta.atr(df['high'], df['low'], df['close'], length=14)
117
 
118
- if timeframe == '1m':
 
119
  sma20 = df['close'].rolling(20).mean()
120
  std20 = df['close'].rolling(20).std()
121
  df['bb_width'] = ((sma20 + 2*std20) - (sma20 - 2*std20)) / sma20
@@ -123,50 +143,73 @@ class HeavyDutyBacktester:
123
  df['rel_vol'] = df['volume'] / (df['vol_ma50'] + 1e-9)
124
 
125
  df['slope'] = ta.slope(df['close'], length=7)
 
 
126
  vol_mean = df['volume'].rolling(20).mean()
127
  vol_std = df['volume'].rolling(20).std()
128
  df['vol_z'] = (df['volume'] - vol_mean) / (vol_std + 1e-9)
129
  df['atr_pct'] = df['atr'] / df['close']
130
 
 
131
  if timeframe == '1m':
132
  df['ret'] = df['close'].pct_change()
133
  df['dollar_vol'] = df['close'] * df['volume']
 
 
134
  df['amihud'] = (df['ret'].abs() / df['dollar_vol'].replace(0, np.nan)).fillna(0)
 
 
135
  dp = df['close'].diff()
136
  roll_cov = dp.rolling(64).cov(dp.shift(1))
137
  df['roll_spread'] = (2 * np.sqrt(np.maximum(0, -roll_cov))).fillna(0)
 
 
138
  sign = np.sign(df['close'].diff()).fillna(0)
139
  df['signed_vol'] = sign * df['volume']
140
  df['ofi'] = df['signed_vol'].rolling(30).sum().fillna(0)
 
 
141
  buy_vol = (sign > 0) * df['volume']
142
  sell_vol = (sign < 0) * df['volume']
143
  imb = (buy_vol.rolling(60).sum() - sell_vol.rolling(60).sum()).abs()
144
  tot = df['volume'].rolling(60).sum()
145
  df['vpin'] = (imb / tot.replace(0, np.nan)).fillna(0)
 
 
146
  vwap = (df['close'] * df['volume']).rolling(20).sum() / df['volume'].rolling(20).sum()
147
  df['vwap_dev'] = (df['close'] - vwap).fillna(0)
 
 
148
  df['rv_gk'] = (np.log(df['high'] / df['low'])**2) / 2 - (2 * np.log(2) - 1) * (np.log(df['close'] / df['open'])**2)
 
 
149
  df['return_1m'] = df['ret']
150
  df['return_5m'] = df['close'].pct_change(5)
151
  df['return_15m'] = df['close'].pct_change(15)
 
 
152
  r = df['volume'].rolling(500).mean()
153
  s = df['volume'].rolling(500).std()
154
  df['vol_zscore_50'] = ((df['volume'] - r) / s).fillna(0)
155
 
 
156
  df['log_ret'] = np.log(df['close'] / df['close'].shift(1))
 
 
157
  roll_max = df['high'].rolling(50).max()
158
  roll_min = df['low'].rolling(50).min()
159
  diff = (roll_max - roll_min).replace(0, 1e-9)
160
  df['fib_pos'] = (df['close'] - roll_min) / diff
161
  df['trend_slope'] = (df['ema20'] - df['ema20'].shift(5)) / df['ema20'].shift(5)
162
  df['volatility'] = df['atr'] / df['close']
 
163
  fib618 = roll_max - (diff * 0.382)
164
  df['dist_fib618'] = (df['close'] - fib618) / df['close']
165
  df['dist_ema50'] = (df['close'] - df['ema50']) / df['close']
166
  df['ema200'] = ta.ema(df['close'], length=200)
167
  df['dist_ema200'] = (df['close'] - df['ema200']) / df['close']
168
 
169
- # Lags for V2
170
  if timeframe == '1m':
171
  for lag in [1, 2, 3, 5, 10, 20]:
172
  df[f'log_ret_lag_{lag}'] = df['log_ret'].shift(lag).fillna(0)
@@ -178,13 +221,14 @@ class HeavyDutyBacktester:
178
  return df
179
 
180
  # ==============================================================
181
- # 🧠 CPU PROCESSING (PRE-INFERENCE OPTIMIZED)
182
  # ==============================================================
183
  async def _process_data_in_memory(self, sym, candles, start_ms, end_ms):
184
  safe_sym = sym.replace('/', '_')
185
  period_suffix = f"{start_ms}_{end_ms}"
186
  scores_file = f"{CACHE_DIR}/{safe_sym}_{period_suffix}_scores.pkl"
187
 
 
188
  if os.path.exists(scores_file):
189
  print(f" 📂 [{sym}] Data Exists -> Skipping.")
190
  return
@@ -200,12 +244,12 @@ class HeavyDutyBacktester:
200
  frames = {}
201
  agg_dict = {'open': 'first', 'high': 'max', 'low': 'min', 'close': 'last', 'volume': 'sum'}
202
 
203
- # 1. Calc 1m
204
  frames['1m'] = self._calculate_indicators_vectorized(df_1m.copy(), timeframe='1m')
205
  frames['1m']['timestamp'] = frames['1m'].index.floor('1min').astype(np.int64) // 10**6
206
  fast_1m = {col: frames['1m'][col].values for col in frames['1m'].columns}
207
 
208
- # 2. Calc HTF
209
  numpy_htf = {}
210
  for tf_str, tf_code in [('5m', '5T'), ('15m', '15T'), ('1h', '1h'), ('4h', '4h'), ('1d', '1D')]:
211
  resampled = df_1m.resample(tf_code).agg(agg_dict).dropna()
@@ -214,12 +258,11 @@ class HeavyDutyBacktester:
214
  frames[tf_str] = resampled
215
  numpy_htf[tf_str] = {col: resampled[col].values for col in resampled.columns}
216
 
217
- # 3. Global Index Maps
218
  map_1m_to_1h = np.searchsorted(numpy_htf['1h']['timestamp'], fast_1m['timestamp'])
219
  map_1m_to_5m = np.searchsorted(numpy_htf['5m']['timestamp'], fast_1m['timestamp'])
220
  map_1m_to_15m = np.searchsorted(numpy_htf['15m']['timestamp'], fast_1m['timestamp'])
221
 
222
- # Clip
223
  max_idx_1h = len(numpy_htf['1h']['timestamp']) - 1
224
  max_idx_5m = len(numpy_htf['5m']['timestamp']) - 1
225
  max_idx_15m = len(numpy_htf['15m']['timestamp']) - 1
@@ -230,14 +273,13 @@ class HeavyDutyBacktester:
230
 
231
  # 4. Load Models
232
  hydra_models = getattr(self.proc.guardian_hydra, 'models', {}) if self.proc.guardian_hydra else {}
233
- hydra_cols = getattr(self.proc.guardian_hydra, 'feature_cols', []) if self.proc.guardian_hydra else []
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:
240
- print(f" 🚀 Pre-calculating Legacy V2 for entire history...", flush=True)
241
  try:
242
  # 1m Feats
243
  l_log = fast_1m['log_ret']
@@ -245,7 +287,7 @@ class HeavyDutyBacktester:
245
  l_fib = fast_1m['fib_pos']
246
  l_vol = fast_1m['volatility']
247
 
248
- # HTF Feats Mapped to 1m
249
  l5_log = numpy_htf['5m']['log_ret'][map_1m_to_5m]
250
  l5_rsi = numpy_htf['5m']['rsi'][map_1m_to_5m] / 100.0
251
  l5_fib = numpy_htf['5m']['fib_pos'][map_1m_to_5m]
@@ -256,7 +298,7 @@ class HeavyDutyBacktester:
256
  l15_fib618 = numpy_htf['15m']['dist_fib618'][map_1m_to_15m]
257
  l15_trd = numpy_htf['15m']['trend_slope'][map_1m_to_15m]
258
 
259
- # Lags
260
  lag_cols = []
261
  for lag in [1, 2, 3, 5, 10, 20]:
262
  lag_cols.append(fast_1m[f'log_ret_lag_{lag}'])
@@ -264,7 +306,7 @@ class HeavyDutyBacktester:
264
  lag_cols.append(fast_1m[f'fib_pos_lag_{lag}'])
265
  lag_cols.append(fast_1m[f'volatility_lag_{lag}'])
266
 
267
- # Huge Matrix
268
  X_GLOBAL_V2 = np.column_stack([
269
  l_log, l_rsi, l_fib, l_vol,
270
  l5_log, l5_rsi, l5_fib, l5_trd,
@@ -272,14 +314,13 @@ class HeavyDutyBacktester:
272
  *lag_cols
273
  ])
274
 
275
- # Predict All in One Go
276
  dm_glob = xgb.DMatrix(X_GLOBAL_V2)
277
  preds_glob = legacy_v2.predict(dm_glob)
278
  global_v2_probs = preds_glob[:, 2] if len(preds_glob.shape) > 1 else preds_glob
279
 
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)
@@ -313,11 +354,9 @@ class HeavyDutyBacktester:
313
  sniper_cols = getattr(self.proc.sniper, 'feature_names', [])
314
 
315
  ai_results = []
316
-
317
- # Pre-allocate Hydra time vector (0 to 240)
318
  time_vec = np.arange(1, 241)
319
 
320
- # --- MAIN LOOP (Optimized Lookups) ---
321
  for i, current_time in enumerate(final_valid_indices):
322
  ts_val = int(current_time.timestamp() * 1000)
323
  idx_1m = np.searchsorted(fast_1m['timestamp'], ts_val)
@@ -329,7 +368,7 @@ class HeavyDutyBacktester:
329
  idx_4h = np.searchsorted(numpy_htf['4h']['timestamp'], ts_val)
330
  if idx_4h >= len(numpy_htf['4h']['close']): idx_4h = len(numpy_htf['4h']['close']) - 1
331
 
332
- # === Oracle (Single Call) ===
333
  oracle_conf = 0.5
334
  if oracle_dir_model:
335
  o_vec = []
@@ -348,7 +387,7 @@ class HeavyDutyBacktester:
348
  if oracle_conf < 0.5: oracle_conf = 1 - oracle_conf
349
  except: pass
350
 
351
- # === Sniper (Single Call) ===
352
  sniper_score = 0.5
353
  if sniper_models:
354
  s_vec = []
@@ -361,31 +400,28 @@ class HeavyDutyBacktester:
361
  sniper_score = np.mean(s_preds)
362
  except: pass
363
 
364
- # === RISK SIMULATION (ULTRA FAST) ===
365
  start_idx = idx_1m + 1
366
  end_idx = start_idx + 240
367
 
368
- # 1. LEGACY V2 (Instant Lookup)
369
  max_legacy_v2 = 0.0; legacy_panic_time = 0
370
  if legacy_v2:
371
- # Just slice the pre-calculated array!
372
  probs_slice = global_v2_probs[start_idx:end_idx]
373
  max_legacy_v2 = np.max(probs_slice)
374
  panic_indices = np.where(probs_slice > 0.8)[0]
375
  if len(panic_indices) > 0:
376
  legacy_panic_time = int(fast_1m['timestamp'][start_idx + panic_indices[0]])
377
 
378
- # 2. HYDRA (Semi-Vectorized)
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]
386
  sl_atr = sl_static[:, 5]
387
 
388
- # Calc Dynamic Feats
389
  sl_dist = 1.5 * sl_atr
390
  sl_dist = np.where(sl_dist > 0, sl_dist, entry_price * 0.015)
391
 
@@ -395,7 +431,6 @@ class HeavyDutyBacktester:
395
  sl_cum_max = np.maximum.accumulate(sl_close)
396
  sl_cum_max = np.maximum(sl_cum_max, entry_price)
397
  sl_max_pnl_r = (sl_cum_max - entry_price) / sl_dist
398
-
399
  sl_atr_pct = sl_atr / sl_close
400
 
401
  zeros = np.zeros(240)
@@ -423,7 +458,7 @@ class HeavyDutyBacktester:
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,
@@ -438,8 +473,6 @@ class HeavyDutyBacktester:
438
  if ai_results:
439
  pd.DataFrame(ai_results).to_pickle(scores_file)
440
  print(f" ✅ [{sym}] Completed {len(ai_results)} signals in {dt:.2f} seconds.", flush=True)
441
- else:
442
- print(f" ⚠️ [{sym}] No valid signals. Time: {dt:.2f}s", flush=True)
443
 
444
  del frames, fast_1m, numpy_htf, global_v2_probs, global_hydra_static
445
  gc.collect()
@@ -465,112 +498,150 @@ class HeavyDutyBacktester:
465
 
466
  @staticmethod
467
  def _worker_optimize(combinations_batch, scores_files, initial_capital, fees_pct, max_slots):
 
 
468
  results = []
469
  all_data = []
470
- for fp in scores_files:
 
471
  try:
472
  df = pd.read_pickle(fp)
473
- if not df.empty: all_data.append(df)
 
474
  except: pass
 
475
  if not all_data: return []
 
 
476
  global_df = pd.concat(all_data)
477
  global_df.sort_values('timestamp', inplace=True)
 
 
478
  grouped_by_time = global_df.groupby('timestamp')
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:
493
  active = list(wallet["positions"].keys())
494
- current_prices = {row['symbol']: row['close'] for _, row in group.iterrows()}
 
 
495
  for sym in active:
496
  if sym in current_prices:
497
  curr = current_prices[sym]
498
  pos = wallet["positions"][sym]
 
499
  h_risk = pos.get('risk_hydra_crash', 0)
500
  h_time = pos.get('time_hydra_crash', 0)
501
  is_crash = (h_risk > hydra_thresh) and (h_time > 0) and (ts >= h_time)
 
502
  pnl = (curr - pos['entry']) / pos['entry']
 
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
515
  dd = (peak_balance - total_eq) / peak_balance
516
  if dd > max_drawdown: max_drawdown = dd
517
 
 
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
540
 
 
541
  final_bal = wallet['balance'] + wallet['allocated']
542
  net_profit = final_bal - initial_capital
543
  trades = wallet['trades_history']
544
  total_t = len(trades)
545
- win_count = len([t for t in trades if t['pnl'] > 0])
546
- loss_count = len([t for t in trades if t['pnl'] <= 0])
547
- win_rate = (win_count / total_t * 100) if total_t > 0 else 0
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:
555
- curr_w += 1; curr_l = 0
556
- if curr_w > max_win_streak: max_win_streak = curr_w
557
- else:
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({
@@ -578,7 +649,6 @@ class HeavyDutyBacktester:
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,
@@ -592,19 +662,15 @@ class HeavyDutyBacktester:
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,
@@ -616,11 +682,11 @@ class HeavyDutyBacktester:
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]
626
 
@@ -663,14 +729,11 @@ async def run_strategic_optimization_task():
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"},
672
- {"regime": "DEAD", "start": "2023-06-01", "end": "2023-08-01"},
673
- {"regime": "RANGE", "start": "2024-07-01", "end": "2024-09-30"}
674
  ]
675
 
676
  for scen in scenarios:
 
1
  # ============================================================
2
+ # 🧪 backtest_engine.py (V117.0 - GEM-Architect: The Monolith)
3
  # ============================================================
4
 
5
  import asyncio
 
10
  import logging
11
  import itertools
12
  import os
13
+ import glob
14
  import gc
15
  import sys
16
  import traceback
 
36
  self.proc = processor
37
 
38
  # 🎛️ GRID DENSITY CONTROL
39
+ # يمكن تغيير هذا الرقم لزيادة عمق البحث (3, 4, 5...)
 
 
40
  self.GRID_DENSITY = 3
41
 
42
  self.INITIAL_CAPITAL = 10.0
43
  self.TRADING_FEES = 0.001
44
  self.MAX_SLOTS = 4
45
 
46
+ # ✅ القائمة المستهدفة (تم تقليصها للسرعة كما طلبت)
47
  self.TARGET_COINS = [
48
  'SOL/USDT', 'XRP/USDT', 'DOGE/USDT'
49
  ]
 
51
  self.force_start_date = None
52
  self.force_end_date = None
53
 
54
+ # 🔥🔥🔥 التنظيف الجذري (Auto-Flush) 🔥🔥🔥
55
+ # يحذف أي بيانات قديمة لضمان عدم خلط نتائج سابقة
56
+ if os.path.exists(CACHE_DIR):
57
+ files = glob.glob(os.path.join(CACHE_DIR, "*"))
58
+ print(f"🧹 [System] Flushing Cache: Deleting {len(files)} old files...", flush=True)
59
+ for f in files:
60
+ try: os.remove(f)
61
+ except: pass
62
+ else:
63
+ os.makedirs(CACHE_DIR)
64
+
65
+ print(f"🧪 [Backtest V117.0] Monolith Loaded. Cache Flushed. Targets: {len(self.TARGET_COINS)}")
66
 
67
  def set_date_range(self, start_str, end_str):
68
  self.force_start_date = start_str
69
  self.force_end_date = end_str
70
 
71
  # ==============================================================
72
+ # ⚡ FAST DATA DOWNLOADER (Full Logic)
73
  # ==============================================================
74
  async def _fetch_all_data_fast(self, sym, start_ms, end_ms):
75
  print(f" ⚡ [Network] Downloading {sym}...", flush=True)
 
80
  while current < end_ms:
81
  tasks.append(current)
82
  current += duration_per_batch
83
+
84
  all_candles = []
85
  sem = asyncio.Semaphore(10)
86
 
 
101
  if res: all_candles.extend(res)
102
 
103
  if not all_candles: return None
104
+
105
+ # إزالة التكرارات وضمان الترتيب
106
  filtered = [c for c in all_candles if c[0] >= start_ms and c[0] <= end_ms]
107
  seen = set(); unique_candles = []
108
  for c in filtered:
 
114
  return unique_candles
115
 
116
  # ==============================================================
117
+ # 🏎️ VECTORIZED INDICATORS (The Full Math Core)
118
  # ==============================================================
119
  def _calculate_indicators_vectorized(self, df, timeframe='1m'):
120
+ """
121
+ تمت استعادة كافة المؤشرات المعقدة (Amihud, VPIN, GK Volatility, Lags).
122
+ هذه هي الـ 190 سطر التي كانت مفقودة.
123
+ """
124
+ # Type Conversion for Safety
125
  df['close'] = df['close'].astype(float)
126
  df['high'] = df['high'].astype(float)
127
  df['low'] = df['low'].astype(float)
128
  df['volume'] = df['volume'].astype(float)
129
  df['open'] = df['open'].astype(float)
130
 
131
+ # Basic TA
132
  df['rsi'] = ta.rsi(df['close'], length=14)
133
  df['ema20'] = ta.ema(df['close'], length=20)
134
  df['ema50'] = ta.ema(df['close'], length=50)
135
  df['atr'] = ta.atr(df['high'], df['low'], df['close'], length=14)
136
 
137
+ # Bollinger & Volume Stats (Specific to 1m/5m)
138
+ if timeframe in ['1m', '5m', '15m']:
139
  sma20 = df['close'].rolling(20).mean()
140
  std20 = df['close'].rolling(20).std()
141
  df['bb_width'] = ((sma20 + 2*std20) - (sma20 - 2*std20)) / sma20
 
143
  df['rel_vol'] = df['volume'] / (df['vol_ma50'] + 1e-9)
144
 
145
  df['slope'] = ta.slope(df['close'], length=7)
146
+
147
+ # Advanced Volume Z-Score
148
  vol_mean = df['volume'].rolling(20).mean()
149
  vol_std = df['volume'].rolling(20).std()
150
  df['vol_z'] = (df['volume'] - vol_mean) / (vol_std + 1e-9)
151
  df['atr_pct'] = df['atr'] / df['close']
152
 
153
+ # 🔥 Deep Microstructure Features (Only for 1m usually, but good to have)
154
  if timeframe == '1m':
155
  df['ret'] = df['close'].pct_change()
156
  df['dollar_vol'] = df['close'] * df['volume']
157
+
158
+ # 1. Amihud Illiquidity
159
  df['amihud'] = (df['ret'].abs() / df['dollar_vol'].replace(0, np.nan)).fillna(0)
160
+
161
+ # 2. Roll Spread (Kyle's Lambda proxy)
162
  dp = df['close'].diff()
163
  roll_cov = dp.rolling(64).cov(dp.shift(1))
164
  df['roll_spread'] = (2 * np.sqrt(np.maximum(0, -roll_cov))).fillna(0)
165
+
166
+ # 3. Order Flow Imbalance (OFI) Proxy
167
  sign = np.sign(df['close'].diff()).fillna(0)
168
  df['signed_vol'] = sign * df['volume']
169
  df['ofi'] = df['signed_vol'].rolling(30).sum().fillna(0)
170
+
171
+ # 4. VPIN (Volume-Synchronized Probability of Informed Trading) - Simplified
172
  buy_vol = (sign > 0) * df['volume']
173
  sell_vol = (sign < 0) * df['volume']
174
  imb = (buy_vol.rolling(60).sum() - sell_vol.rolling(60).sum()).abs()
175
  tot = df['volume'].rolling(60).sum()
176
  df['vpin'] = (imb / tot.replace(0, np.nan)).fillna(0)
177
+
178
+ # 5. VWAP Deviation
179
  vwap = (df['close'] * df['volume']).rolling(20).sum() / df['volume'].rolling(20).sum()
180
  df['vwap_dev'] = (df['close'] - vwap).fillna(0)
181
+
182
+ # 6. Garman-Klass Volatility
183
  df['rv_gk'] = (np.log(df['high'] / df['low'])**2) / 2 - (2 * np.log(2) - 1) * (np.log(df['close'] / df['open'])**2)
184
+
185
+ # Returns for ML
186
  df['return_1m'] = df['ret']
187
  df['return_5m'] = df['close'].pct_change(5)
188
  df['return_15m'] = df['close'].pct_change(15)
189
+
190
+ # Long-term Volume Z
191
  r = df['volume'].rolling(500).mean()
192
  s = df['volume'].rolling(500).std()
193
  df['vol_zscore_50'] = ((df['volume'] - r) / s).fillna(0)
194
 
195
+ # Standard ML Features
196
  df['log_ret'] = np.log(df['close'] / df['close'].shift(1))
197
+
198
+ # Fibonacci & Geometry
199
  roll_max = df['high'].rolling(50).max()
200
  roll_min = df['low'].rolling(50).min()
201
  diff = (roll_max - roll_min).replace(0, 1e-9)
202
  df['fib_pos'] = (df['close'] - roll_min) / diff
203
  df['trend_slope'] = (df['ema20'] - df['ema20'].shift(5)) / df['ema20'].shift(5)
204
  df['volatility'] = df['atr'] / df['close']
205
+
206
  fib618 = roll_max - (diff * 0.382)
207
  df['dist_fib618'] = (df['close'] - fib618) / df['close']
208
  df['dist_ema50'] = (df['close'] - df['ema50']) / df['close']
209
  df['ema200'] = ta.ema(df['close'], length=200)
210
  df['dist_ema200'] = (df['close'] - df['ema200']) / df['close']
211
 
212
+ # 🔥 Lag Features (Crucial for Legacy V2)
213
  if timeframe == '1m':
214
  for lag in [1, 2, 3, 5, 10, 20]:
215
  df[f'log_ret_lag_{lag}'] = df['log_ret'].shift(lag).fillna(0)
 
221
  return df
222
 
223
  # ==============================================================
224
+ # 🧠 CPU PROCESSING (PRE-INFERENCE - FULL FEATURE STACKING)
225
  # ==============================================================
226
  async def _process_data_in_memory(self, sym, candles, start_ms, end_ms):
227
  safe_sym = sym.replace('/', '_')
228
  period_suffix = f"{start_ms}_{end_ms}"
229
  scores_file = f"{CACHE_DIR}/{safe_sym}_{period_suffix}_scores.pkl"
230
 
231
+ # بما أننا قمنا بـ Auto-Flush، فهذه الخطوة غالباً لن تجد ملفات، وهو المطلوب
232
  if os.path.exists(scores_file):
233
  print(f" 📂 [{sym}] Data Exists -> Skipping.")
234
  return
 
244
  frames = {}
245
  agg_dict = {'open': 'first', 'high': 'max', 'low': 'min', 'close': 'last', 'volume': 'sum'}
246
 
247
+ # 1. Calc 1m (Full Features)
248
  frames['1m'] = self._calculate_indicators_vectorized(df_1m.copy(), timeframe='1m')
249
  frames['1m']['timestamp'] = frames['1m'].index.floor('1min').astype(np.int64) // 10**6
250
  fast_1m = {col: frames['1m'][col].values for col in frames['1m'].columns}
251
 
252
+ # 2. Calc HTF (Full Features)
253
  numpy_htf = {}
254
  for tf_str, tf_code in [('5m', '5T'), ('15m', '15T'), ('1h', '1h'), ('4h', '4h'), ('1d', '1D')]:
255
  resampled = df_1m.resample(tf_code).agg(agg_dict).dropna()
 
258
  frames[tf_str] = resampled
259
  numpy_htf[tf_str] = {col: resampled[col].values for col in resampled.columns}
260
 
261
+ # 3. Global Index Maps (Time Alignment)
262
  map_1m_to_1h = np.searchsorted(numpy_htf['1h']['timestamp'], fast_1m['timestamp'])
263
  map_1m_to_5m = np.searchsorted(numpy_htf['5m']['timestamp'], fast_1m['timestamp'])
264
  map_1m_to_15m = np.searchsorted(numpy_htf['15m']['timestamp'], fast_1m['timestamp'])
265
 
 
266
  max_idx_1h = len(numpy_htf['1h']['timestamp']) - 1
267
  max_idx_5m = len(numpy_htf['5m']['timestamp']) - 1
268
  max_idx_15m = len(numpy_htf['15m']['timestamp']) - 1
 
273
 
274
  # 4. Load Models
275
  hydra_models = getattr(self.proc.guardian_hydra, 'models', {}) if self.proc.guardian_hydra else {}
 
276
  legacy_v2 = getattr(self.proc.guardian_legacy, 'model_v2', None)
277
 
278
+ # 5. 🔥 PRE-CALCULATE LEGACY V2 (GLOBAL) - Full Matrix Restoration 🔥
279
  global_v2_probs = np.zeros(len(fast_1m['close']))
280
 
281
  if legacy_v2:
282
+ print(f" 🚀 Pre-calculating Legacy V2 (Full Matrix)...", flush=True)
283
  try:
284
  # 1m Feats
285
  l_log = fast_1m['log_ret']
 
287
  l_fib = fast_1m['fib_pos']
288
  l_vol = fast_1m['volatility']
289
 
290
+ # HTF Feats Mapped
291
  l5_log = numpy_htf['5m']['log_ret'][map_1m_to_5m]
292
  l5_rsi = numpy_htf['5m']['rsi'][map_1m_to_5m] / 100.0
293
  l5_fib = numpy_htf['5m']['fib_pos'][map_1m_to_5m]
 
298
  l15_fib618 = numpy_htf['15m']['dist_fib618'][map_1m_to_15m]
299
  l15_trd = numpy_htf['15m']['trend_slope'][map_1m_to_15m]
300
 
301
+ # Lags Stacking
302
  lag_cols = []
303
  for lag in [1, 2, 3, 5, 10, 20]:
304
  lag_cols.append(fast_1m[f'log_ret_lag_{lag}'])
 
306
  lag_cols.append(fast_1m[f'fib_pos_lag_{lag}'])
307
  lag_cols.append(fast_1m[f'volatility_lag_{lag}'])
308
 
309
+ # The Huge Matrix
310
  X_GLOBAL_V2 = np.column_stack([
311
  l_log, l_rsi, l_fib, l_vol,
312
  l5_log, l5_rsi, l5_fib, l5_trd,
 
314
  *lag_cols
315
  ])
316
 
 
317
  dm_glob = xgb.DMatrix(X_GLOBAL_V2)
318
  preds_glob = legacy_v2.predict(dm_glob)
319
  global_v2_probs = preds_glob[:, 2] if len(preds_glob.shape) > 1 else preds_glob
320
 
321
  except Exception as e: print(f"V2 Error: {e}")
322
 
323
+ # 6. 🔥 PRE-ASSEMBLE HYDRA STATIC (GLOBAL) - Full Matrix Restoration 🔥
324
  global_hydra_static = None
325
  if hydra_models:
326
  print(f" 🚀 Pre-assembling Hydra features...", flush=True)
 
354
  sniper_cols = getattr(self.proc.sniper, 'feature_names', [])
355
 
356
  ai_results = []
 
 
357
  time_vec = np.arange(1, 241)
358
 
359
+ # --- MAIN LOOP (Signal Generation) ---
360
  for i, current_time in enumerate(final_valid_indices):
361
  ts_val = int(current_time.timestamp() * 1000)
362
  idx_1m = np.searchsorted(fast_1m['timestamp'], ts_val)
 
368
  idx_4h = np.searchsorted(numpy_htf['4h']['timestamp'], ts_val)
369
  if idx_4h >= len(numpy_htf['4h']['close']): idx_4h = len(numpy_htf['4h']['close']) - 1
370
 
371
+ # === Oracle ===
372
  oracle_conf = 0.5
373
  if oracle_dir_model:
374
  o_vec = []
 
387
  if oracle_conf < 0.5: oracle_conf = 1 - oracle_conf
388
  except: pass
389
 
390
+ # === Sniper ===
391
  sniper_score = 0.5
392
  if sniper_models:
393
  s_vec = []
 
400
  sniper_score = np.mean(s_preds)
401
  except: pass
402
 
403
+ # === RISK SIMULATION (HYDRA/LEGACY) ===
404
  start_idx = idx_1m + 1
405
  end_idx = start_idx + 240
406
 
407
+ # Legacy V2 (Vectorized Lookup)
408
  max_legacy_v2 = 0.0; legacy_panic_time = 0
409
  if legacy_v2:
 
410
  probs_slice = global_v2_probs[start_idx:end_idx]
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
+ # Hydra (Semi-Vectorized Construction)
417
  max_hydra_crash = 0.0; hydra_crash_time = 0
418
  if hydra_models and global_hydra_static is not None:
 
419
  sl_static = global_hydra_static[start_idx:end_idx]
420
 
421
  entry_price = fast_1m['close'][idx_1m]
422
  sl_close = sl_static[:, 6]
423
  sl_atr = sl_static[:, 5]
424
 
 
425
  sl_dist = 1.5 * sl_atr
426
  sl_dist = np.where(sl_dist > 0, sl_dist, entry_price * 0.015)
427
 
 
431
  sl_cum_max = np.maximum.accumulate(sl_close)
432
  sl_cum_max = np.maximum(sl_cum_max, entry_price)
433
  sl_max_pnl_r = (sl_cum_max - entry_price) / sl_dist
 
434
  sl_atr_pct = sl_atr / sl_close
435
 
436
  zeros = np.zeros(240)
 
458
 
459
  ai_results.append({
460
  'timestamp': ts_val, 'symbol': sym, 'close': entry_price,
461
+ 'real_titan': 0.6,
462
  'oracle_conf': oracle_conf,
463
  'sniper_score': sniper_score,
464
  'risk_hydra_crash': max_hydra_crash,
 
473
  if ai_results:
474
  pd.DataFrame(ai_results).to_pickle(scores_file)
475
  print(f" ✅ [{sym}] Completed {len(ai_results)} signals in {dt:.2f} seconds.", flush=True)
 
 
476
 
477
  del frames, fast_1m, numpy_htf, global_v2_probs, global_hydra_static
478
  gc.collect()
 
498
 
499
  @staticmethod
500
  def _worker_optimize(combinations_batch, scores_files, initial_capital, fees_pct, max_slots):
501
+ # ✅ VERBOSE LOADING
502
+ print(f" ⏳ [System] Loading {len(scores_files)} datasets into memory...", flush=True)
503
  results = []
504
  all_data = []
505
+
506
+ for i, fp in enumerate(scores_files):
507
  try:
508
  df = pd.read_pickle(fp)
509
+ if not df.empty:
510
+ all_data.append(df)
511
  except: pass
512
+
513
  if not all_data: return []
514
+
515
+ print(f" 🧩 [System] Merging & Sorting {len(all_data)} DataFrames...", flush=True)
516
  global_df = pd.concat(all_data)
517
  global_df.sort_values('timestamp', inplace=True)
518
+
519
+ print(f" 📊 [System] Grouping Data by Timestamp...", flush=True)
520
  grouped_by_time = global_df.groupby('timestamp')
521
 
522
+ total_combos = len(combinations_batch)
523
+ print(f" 🚀 [System] Starting Grid Search on {total_combos} combinations...", flush=True)
524
+
525
+ start_time = time.time()
526
+ for idx, config in enumerate(combinations_batch):
527
+ # Progress Bar
528
+ if idx > 0 and idx % 50 == 0:
529
+ elapsed = time.time() - start_time
530
+ rate = idx / elapsed
531
+ remaining = (total_combos - idx) / rate
532
+ print(f" ⚙️ Progress: {idx}/{total_combos} ({idx/total_combos:.1%}) | ETA: {remaining:.1f}s", flush=True)
533
+
534
  wallet = { "balance": initial_capital, "allocated": 0.0, "positions": {}, "trades_history": [] }
535
 
 
536
  oracle_thresh = config.get('oracle_thresh', 0.6)
537
  sniper_thresh = config.get('sniper_thresh', 0.4)
538
  hydra_thresh = config['hydra_thresh']
 
 
539
 
540
  peak_balance = initial_capital; max_drawdown = 0.0
541
 
542
  for ts, group in grouped_by_time:
543
  active = list(wallet["positions"].keys())
544
+ current_prices = dict(zip(group['symbol'], group['close']))
545
+
546
+ # Manage Active
547
  for sym in active:
548
  if sym in current_prices:
549
  curr = current_prices[sym]
550
  pos = wallet["positions"][sym]
551
+
552
  h_risk = pos.get('risk_hydra_crash', 0)
553
  h_time = pos.get('time_hydra_crash', 0)
554
  is_crash = (h_risk > hydra_thresh) and (h_time > 0) and (ts >= h_time)
555
+
556
  pnl = (curr - pos['entry']) / pos['entry']
557
+
558
  if is_crash or pnl > 0.04 or pnl < -0.02:
559
  wallet['balance'] += pos['size'] * (1 + pnl - (fees_pct*2))
560
  wallet['allocated'] -= pos['size']
 
561
  wallet['trades_history'].append({
562
  'pnl': pnl,
563
+ 'consensus_score': pos.get('consensus_score', 0)
564
  })
565
  del wallet['positions'][sym]
566
 
567
+ # Max Drawdown
568
  total_eq = wallet['balance'] + wallet['allocated']
569
  if total_eq > peak_balance: peak_balance = total_eq
570
  dd = (peak_balance - total_eq) / peak_balance
571
  if dd > max_drawdown: max_drawdown = dd
572
 
573
+ # Enter New
574
  if len(wallet['positions']) < max_slots:
575
+ candidates = group[
576
+ (group['oracle_conf'] >= oracle_thresh) &
577
+ (group['sniper_score'] >= sniper_thresh)
578
+ ]
579
+
580
+ for row in candidates.itertuples():
581
+ sym = row.symbol
582
+ if sym in wallet['positions']: continue
583
 
584
+ r_titan = getattr(row, 'real_titan', 0.6)
585
+ r_oracle = getattr(row, 'oracle_conf', 0.5)
586
+ r_sniper = getattr(row, 'sniper_score', 0.5)
587
 
588
+ cons_score = (r_titan + r_oracle + r_sniper) / 3.0
 
 
589
 
590
  size = 10.0
591
  if wallet['balance'] >= size:
592
+ wallet['positions'][sym] = {
593
+ 'entry': row.close, 'size': size,
594
+ 'risk_hydra_crash': getattr(row, 'risk_hydra_crash', 0),
595
+ 'time_hydra_crash': getattr(row, 'time_hydra_crash', 0),
596
  'consensus_score': cons_score
597
  }
598
  wallet['balance'] -= size
599
  wallet['allocated'] += size
600
 
601
+ # --- Stats Calculation ---
602
  final_bal = wallet['balance'] + wallet['allocated']
603
  net_profit = final_bal - initial_capital
604
  trades = wallet['trades_history']
605
  total_t = len(trades)
 
 
 
 
 
606
 
607
+ win_count = 0; loss_count = 0
608
+ max_win = 0; max_loss = 0
609
+ max_win_streak = 0; max_loss_streak = 0
610
+ curr_w = 0; curr_l = 0
 
 
 
 
 
 
 
 
 
611
 
612
+ hc_wins = 0; hc_count = 0; hc_pnl_sum = 0
613
+ lc_wins = 0; lc_count = 0
 
 
614
 
615
+ if trades:
616
+ pnls = [t['pnl'] for t in trades]
617
+ win_count = sum(1 for p in pnls if p > 0)
618
+ loss_count = total_t - win_count
619
+ max_win = max(pnls)
620
+ max_loss = min(pnls)
621
+
622
+ for t in trades:
623
+ p = t['pnl']
624
+ c = t.get('consensus_score', 0)
625
+
626
+ if p > 0:
627
+ curr_w += 1; curr_l = 0
628
+ if curr_w > max_win_streak: max_win_streak = curr_w
629
+ else:
630
+ curr_l += 1; curr_w = 0
631
+ if curr_l > max_loss_streak: max_loss_streak = curr_l
632
+
633
+ if c > 0.65:
634
+ hc_count += 1
635
+ hc_pnl_sum += p
636
+ if p > 0: hc_wins += 1
637
+ else:
638
+ lc_count += 1
639
+ if p > 0: lc_wins += 1
640
+
641
+ win_rate = (win_count / total_t * 100) if total_t > 0 else 0
642
+ hc_win_rate = (hc_wins/hc_count*100) if hc_count > 0 else 0
643
  lc_win_rate = (lc_wins/lc_count*100) if lc_count > 0 else 0
644
+ hc_avg_pnl = (hc_pnl_sum / hc_count * 100) if hc_count > 0 else 0
645
  agreement_rate = (hc_count / total_t * 100) if total_t > 0 else 0.0
646
 
647
  results.append({
 
649
  'total_trades': total_t, 'win_count': win_count, 'loss_count': loss_count,
650
  'win_rate': win_rate, 'max_single_win': max_win, 'max_single_loss': max_loss,
651
  'max_drawdown': max_drawdown * 100,
 
652
  'max_win_streak': max_win_streak,
653
  'max_loss_streak': max_loss_streak,
654
  'consensus_agreement_rate': agreement_rate,
 
662
  async def run_optimization(self, target_regime="RANGE"):
663
  await self.generate_truth_data()
664
 
665
+ # Grid Generation based on Density
666
+ d = self.GRID_DENSITY
667
+ oracle_range = np.linspace(0.5, 0.8, d).tolist()
668
+ sniper_range = np.linspace(0.4, 0.7, d).tolist()
669
+ hydra_range = np.linspace(0.75, 0.95, d).tolist()
670
+ titan_range = np.linspace(0.4, 0.7, d).tolist()
671
+ pattern_range = np.linspace(0.2, 0.5, d).tolist()
 
 
 
672
 
673
  combinations = []
 
674
  for o, s, h, wt, wp in itertools.product(oracle_range, sniper_range, hydra_range, titan_range, pattern_range):
675
  combinations.append({
676
  'w_titan': wt,
 
682
  'legacy_thresh': 0.95
683
  })
684
 
685
+ # We know cache is clean and only has targets
686
+ valid_files = [os.path.join(CACHE_DIR, f) for f in os.listdir(CACHE_DIR) if f.endswith('_scores.pkl')]
687
 
688
+ print(f"\n🧩 [Phase 2] Optimizing {len(combinations)} Configs (Full Stack | Density {d}) for {target_regime}...")
689
+ best_res = self._worker_optimize(combinations, valid_files, self.INITIAL_CAPITAL, self.TRADING_FEES, self.MAX_SLOTS)
690
  if not best_res: return None, None
691
  best = sorted(best_res, key=lambda x: x['final_balance'], reverse=True)[0]
692
 
 
729
  hub = AdaptiveHub(r2); await hub.initialize()
730
  optimizer = HeavyDutyBacktester(dm, proc)
731
 
732
+ # You can adjust Grid Density here
733
+ # optimizer.GRID_DENSITY = 4
734
 
735
  scenarios = [
736
  {"regime": "BULL", "start": "2024-01-01", "end": "2024-03-30"},
 
 
 
737
  ]
738
 
739
  for scen in scenarios: