Riy777 commited on
Commit
243ec32
·
verified ·
1 Parent(s): e15b993

Update backtest_engine.py

Browse files
Files changed (1) hide show
  1. backtest_engine.py +84 -76
backtest_engine.py CHANGED
@@ -1,5 +1,5 @@
1
  # ============================================================
2
- # 🧪 backtest_engine.py (V160.0 - GEM-Architect: Gov + L1 Update)
3
  # ============================================================
4
 
5
  import asyncio
@@ -59,19 +59,19 @@ class HeavyDutyBacktester:
59
  self.dm = data_manager
60
  self.proc = processor
61
 
62
- # 🎛️ الكثافة (Density): عدد الخطوات في النطاق
63
  self.GRID_DENSITY = 3
64
 
65
  self.INITIAL_CAPITAL = 10.0
66
  self.TRADING_FEES = 0.001
67
  self.MAX_SLOTS = 4
68
 
69
- # 🎛️ CONTROL PANEL - DYNAMIC RANGES
70
  self.GRID_RANGES = {
71
  'TITAN': np.linspace(0.20, 0.50, self.GRID_DENSITY),
72
  'ORACLE': np.linspace(0.50, 0.80, self.GRID_DENSITY),
73
  'SNIPER': np.linspace(0.30, 0.70, self.GRID_DENSITY),
74
- 'GOV_SCORE': np.linspace(50.0, 80.0, self.GRID_DENSITY), # ✅ Added Governance Threshold
75
  }
76
 
77
  self.TARGET_COINS = [
@@ -86,13 +86,13 @@ class HeavyDutyBacktester:
86
  ]
87
 
88
  # ✅ DATE SETTINGS
89
- self.USE_FIXED_DATES = False # Set to True to use force_start_date
90
- self.LOOKBACK_DAYS = 30 # Default lookback
91
  self.force_start_date = "2024-01-01"
92
  self.force_end_date = "2024-02-01"
93
 
94
  if not os.path.exists(CACHE_DIR): os.makedirs(CACHE_DIR)
95
- print(f"🧪 [Backtest V160.0] Hyper-Speed Jump Engine (Gov + L1 Update).")
96
 
97
  def set_date_range(self, start_str, end_str):
98
  self.force_start_date = start_str
@@ -131,14 +131,14 @@ class HeavyDutyBacktester:
131
  return df.values.tolist()
132
 
133
  # ----------------------------------------------------------------------
134
- # 🏎️ VECTORIZED INDICATORS (Enhanced for L1 & Gov)
135
  # ----------------------------------------------------------------------
136
  def _calculate_indicators_vectorized(self, df, timeframe='1m'):
137
  if df.empty: return df
138
  cols = ['close', 'high', 'low', 'volume', 'open']
139
  for c in cols: df[c] = df[c].astype(np.float64)
140
 
141
- # Basic EMAs (Required for L1 & Gov)
142
  df['ema9'] = df['close'].ewm(span=9, adjust=False).mean()
143
  df['ema20'] = df['close'].ewm(span=20, adjust=False).mean()
144
  df['ema21'] = df['close'].ewm(span=21, adjust=False).mean()
@@ -159,33 +159,36 @@ class HeavyDutyBacktester:
159
  macd = ta.macd(df['close'])
160
  if macd is not None:
161
  df['MACD'] = macd.iloc[:, 0].fillna(0)
162
- df['MACD_s'] = macd.iloc[:, 2].fillna(0) # Signal line
163
  df['MACD_h'] = macd.iloc[:, 1].fillna(0)
164
  else: df['MACD'] = 0; df['MACD_h'] = 0; df['MACD_s'] = 0
165
 
 
166
  df['ADX'] = ta.adx(df['high'], df['low'], df['close'], length=14).iloc[:, 0].fillna(0)
 
 
167
  df['CCI'] = ta.cci(df['high'], df['low'], df['close'], length=20).fillna(0)
168
  df['MFI'] = ta.mfi(df['high'], df['low'], df['close'], df['volume'], length=14).fillna(50)
169
- df['slope'] = ta.slope(df['close'], length=7).fillna(0)
170
  vwap = ta.vwap(df['high'], df['low'], df['close'], df['volume'])
171
  df['vwap'] = vwap.fillna(df['close']) if vwap is not None else df['close']
172
 
173
  c = df['close'].values
174
- # Distances for L1 Logic
175
- df['dist_ema50'] = (df['ema50'] - c) / df['ema50'] # Positive if price below ema50
176
  df['dist_upper'] = (df['upper_bb'] - c) / c
177
 
178
- # Features for models
179
  df['EMA_9_dist'] = (c / df['ema9'].values) - 1
180
  df['EMA_21_dist'] = (c / df['ema21'].values) - 1
181
  df['EMA_50_dist'] = (c / df['ema50'].values) - 1
182
  df['EMA_200_dist'] = (c / df['ema200'].values) - 1
183
  df['VWAP_dist'] = (c / df['vwap'].values) - 1
184
- df['ATR_pct'] = df['ATR'] / (c + 1e-9)
185
 
186
  if timeframe == '1d': df['Trend_Strong'] = np.where(df['ADX'] > 25, 1.0, 0.0)
187
 
188
  df['vol_z'] = _z_roll_np(df['volume'].values, 20)
 
189
  df['rel_vol'] = df['volume'] / (df['volume'].rolling(50).mean() + 1e-9)
190
  df['log_ret'] = np.concatenate([[0], np.diff(np.log(c + 1e-9))])
191
 
@@ -237,43 +240,63 @@ class HeavyDutyBacktester:
237
  map_1h = get_map('1h'); map_15m = get_map('15m')
238
 
239
  # ============================================================
240
- # 2. NEW L1 LOGIC (Vectorized Implementation of DataManager)
 
 
 
 
 
 
 
 
 
 
 
 
241
  # ============================================================
242
- # Logic is applied on 1H frame, mapped to 1m
 
 
 
243
  h1_rsi = numpy_htf['1h']['RSI'][map_1h]
244
  h1_close = numpy_htf['1h']['close'][map_1h]
 
 
245
  h1_ema20 = numpy_htf['1h']['ema20'][map_1h]
246
  h1_ema50 = numpy_htf['1h']['ema50'][map_1h]
247
  h1_ema200 = numpy_htf['1h']['ema200'][map_1h]
248
- h1_lower_bb = numpy_htf['1h']['lower_bb'][map_1h]
249
- h1_upper_bb = numpy_htf['1h']['upper_bb'][map_1h]
250
- h1_bb_width = numpy_htf['1h']['bb_width'][map_1h]
 
 
 
 
 
251
 
252
- # TYPE 1: SAFE_BOTTOM
253
- # (rsi < 45) & (close <= lower_bb * 1.05) & (dist_from_ema50 > 0.015)
254
- dist_from_ema50 = (h1_ema50 - h1_close) / h1_ema50
255
- mask_safe_bottom = (h1_rsi < 45) & (h1_close <= h1_lower_bb * 1.05) & (dist_from_ema50 > 0.015)
256
 
257
- # TYPE 2: ACCUMULATION_SQUEEZE
258
- # (45 <= rsi <= 60) & (bb_width < 0.12) & (close > ema20 * 0.995)
259
- mask_acc_squeeze = (h1_rsi >= 45) & (h1_rsi <= 60) & (h1_bb_width < 0.12) & (h1_close > h1_ema20 * 0.995)
260
 
261
- # TYPE 3: MOMENTUM_LAUNCH
262
- # (60 < rsi < 80) & (close > ema50) & (close > ema200) & (dist_to_upper < 0.08)
263
- dist_to_upper = (h1_upper_bb - h1_close) / h1_close
264
- mask_mom_launch = (h1_rsi > 60) & (h1_rsi < 80) & (h1_close > h1_ema50) & (h1_close > h1_ema200) & (dist_to_upper < 0.08)
 
 
 
 
 
265
 
266
- # Combine Masks (Any Valid L1)
267
- valid_l1_mask = mask_safe_bottom | mask_acc_squeeze | mask_mom_launch
268
-
269
  # ============================================================
270
- # ✅ 4. GOVERNANCE PROXY (Vectorized Scoring)
271
  # ============================================================
272
- # Mimics governance_engine.py logic (Simplified for speed)
273
  gov_points = np.zeros(len(arr_ts_1m), dtype=np.float32)
274
 
275
- # Trend (30%)
276
- # EMA9 > EMA21 on 15m
277
  m15_ema9 = numpy_htf['15m']['ema9'][map_15m]
278
  m15_ema21 = numpy_htf['15m']['ema21'][map_15m]
279
  m15_ema50 = numpy_htf['15m']['ema50'][map_15m]
@@ -283,7 +306,6 @@ class HeavyDutyBacktester:
283
  gov_points += np.where(m15_ema21 > m15_ema50, 10.0, 0.0)
284
  gov_points += np.where(m15_close > numpy_htf['15m']['ema200'][map_15m], 5.0, 0.0)
285
 
286
- # Momentum (30%)
287
  m15_rsi = numpy_htf['15m']['RSI'][map_15m]
288
  m15_macd = numpy_htf['15m']['MACD'][map_15m]
289
  m15_macd_s = numpy_htf['15m']['MACD_s'][map_15m]
@@ -291,16 +313,11 @@ class HeavyDutyBacktester:
291
  gov_points += np.where((m15_rsi > 45) & (m15_rsi < 70), 15.0, 0.0)
292
  gov_points += np.where(m15_macd > m15_macd_s, 15.0, 0.0)
293
 
294
- # Volatility & Volume (20%)
295
  m15_bbw = numpy_htf['15m']['bb_width'][map_15m]
296
- gov_points += np.where((m15_bbw > 0.02) & (m15_bbw < 0.15), 10.0, 0.0) # Not too tight, not too wide
297
  gov_points += np.where(numpy_htf['15m']['rel_vol'][map_15m] > 1.0, 10.0, 0.0)
298
-
299
- # Structure (20%)
300
- # Price above VWAP
301
  gov_points += np.where(m15_close > numpy_htf['15m']['vwap'][map_15m], 20.0, 0.0)
302
 
303
- # Final Governance Score (0-100)
304
  gov_scores_final = np.clip(gov_points, 0, 100)
305
 
306
  # ============================================================
@@ -314,7 +331,6 @@ class HeavyDutyBacktester:
314
 
315
  global_titan_scores = np.full(len(arr_ts_1m), 0.5, dtype=np.float32)
316
  if titan_model:
317
- # (Titan prediction logic kept same as original for stability)
318
  titan_cols = [
319
  '5m_open', '5m_high', '5m_low', '5m_close', '5m_volume', '5m_RSI', '5m_MACD', '5m_MACD_h',
320
  '5m_CCI', '5m_ADX', '5m_EMA_9_dist', '5m_EMA_21_dist', '5m_EMA_50_dist', '5m_EMA_200_dist',
@@ -326,7 +342,6 @@ class HeavyDutyBacktester:
326
  '1d_RSI', '1d_EMA_200_dist', '1d_Trend_Strong'
327
  ]
328
  try:
329
- # Need map_5m and map_4h here strictly for Titan
330
  def get_map_local(tf):
331
  if tf not in numpy_htf: return np.zeros(len(arr_ts_1m), dtype=int)
332
  return np.clip(np.searchsorted(numpy_htf[tf]['timestamp'], arr_ts_1m), 0, len(numpy_htf[tf]['timestamp']) - 1)
@@ -348,7 +363,6 @@ class HeavyDutyBacktester:
348
  global_oracle_scores = np.full(len(arr_ts_1m), 0.5, dtype=np.float32)
349
  if oracle_dir:
350
  try:
351
- # Need map_4h for Oracle too
352
  map_4h = locals().get('map_4h', get_map('4h'))
353
  o_vecs = []
354
  for col in oracle_cols:
@@ -378,19 +392,16 @@ class HeavyDutyBacktester:
378
  global_sniper_scores = _revive_score_distribution(np.mean(preds, axis=0))
379
  except: pass
380
 
381
- # 3. DISABLE GUARDIANS IN BACKTEST (Requested Update)
382
- # We skip Hydra and Legacy V2 predictions to save time and because user requested.
383
-
384
- # Filter (Refined with new L1)
385
- # Keep candles where L1 is Valid OR Scores are very high
386
- is_candidate_mask = valid_l1_mask | ((global_titan_scores > 0.6) & (global_oracle_scores > 0.6))
387
 
388
  candidate_indices = np.where(is_candidate_mask)[0]
389
  end_limit = len(arr_ts_1m) - 60
390
  candidate_indices = candidate_indices[candidate_indices < end_limit]
391
  candidate_indices = candidate_indices[candidate_indices >= 500]
392
 
393
- print(f" 🌪️ Final List: {len(candidate_indices)} candidates ready for testing.", flush=True)
394
 
395
  ai_results = pd.DataFrame({
396
  'timestamp': arr_ts_1m[candidate_indices],
@@ -399,8 +410,9 @@ class HeavyDutyBacktester:
399
  'real_titan': global_titan_scores[candidate_indices],
400
  'oracle_conf': global_oracle_scores[candidate_indices],
401
  'sniper_score': global_sniper_scores[candidate_indices],
402
- 'gov_score': gov_scores_final[candidate_indices], # ✅ New Gov Score
403
- 'l1_valid': valid_l1_mask[candidate_indices].astype(int) # New L1 Flag
 
404
  })
405
 
406
  dt = time.time() - t0
@@ -431,7 +443,7 @@ class HeavyDutyBacktester:
431
 
432
  @staticmethod
433
  def _worker_optimize(combinations_batch, scores_files, initial_capital, fees_pct, max_slots):
434
- """🚀 HYPER-SPEED JUMP LOGIC (NO GUARDIANS, ADDED GOV)"""
435
  print(f" ⏳ [System] Loading {len(scores_files)} datasets...", flush=True)
436
  data = []
437
  for f in scores_files:
@@ -449,16 +461,19 @@ class HeavyDutyBacktester:
449
  oracle = df['oracle_conf'].values
450
  sniper = df['sniper_score'].values
451
  titan = df['real_titan'].values
452
- gov_s = df['gov_score'].values # ✅ Loaded Gov Score
453
- l1_v = df['l1_valid'].values # Loaded L1 Valid Flag
 
454
 
455
  N = len(ts)
456
  print(f" 🚀 [System] Testing {len(combinations_batch)} configs on {N} candidates...", flush=True)
457
 
458
  res = []
459
  for cfg in combinations_batch:
460
- # ✅ Updated Entry Mask: Requires Valid L1 & Gov Score
461
- entry_mask = (l1_v == 1) & \
 
 
462
  (gov_s >= cfg['GOV_SCORE']) & \
463
  (oracle >= cfg['ORACLE']) & \
464
  (sniper >= cfg['SNIPER']) & \
@@ -466,8 +481,7 @@ class HeavyDutyBacktester:
466
 
467
  valid_entry_indices = np.where(entry_mask)[0]
468
 
469
- # Simulation State
470
- pos = {} # sym_id -> (entry_price, size)
471
  bal = float(initial_capital)
472
  alloc = 0.0
473
  log = []
@@ -475,12 +489,11 @@ class HeavyDutyBacktester:
475
  for i in range(N):
476
  s = sym_id[i]; p = float(close[i])
477
 
478
- # A. Check Exits (Standard TP/SL Logic only)
479
  if s in pos:
480
  entry_p, size_val = pos[s]
481
  pnl = (p - entry_p) / entry_p
482
 
483
- # Exit Rules (Standard Backtest)
484
  if (pnl > 0.04) or (pnl < -0.02):
485
  realized = pnl - (fees_pct * 2)
486
  bal += size_val * (1.0 + realized)
@@ -540,13 +553,13 @@ class HeavyDutyBacktester:
540
 
541
  mapped_config = {
542
  'w_titan': best['config']['TITAN'],
543
- 'w_struct': 0.3, # Fixed as removed from grid
544
- 'thresh': 50.0, # Fixed L1 Logic
545
  'oracle_thresh': best['config']['ORACLE'],
546
  'sniper_thresh': best['config']['SNIPER'],
547
  'gov_thresh': best['config']['GOV_SCORE'],
548
- 'hydra_thresh': 0.85, # Default Safe
549
- 'legacy_thresh': 0.95 # Default Safe
550
  }
551
 
552
  # Diagnosis
@@ -585,14 +598,9 @@ async def run_strategic_optimization_task():
585
  hub = AdaptiveHub(r2); await hub.initialize()
586
  opt = HeavyDutyBacktester(dm, proc)
587
 
588
- # Scenarios now just set the regime target for saving, dates handled internally
589
- scenarios = [
590
- {"regime": "RANGE"},
591
- # Add more regimes if needed to run consecutively
592
- ]
593
 
594
  for s in scenarios:
595
- # Dates are now handled by LOOKBACK_DAYS in constructor by default
596
  best_cfg, best_stats = await opt.run_optimization(s["regime"])
597
  if best_cfg: hub.submit_challenger(s["regime"], best_cfg, best_stats)
598
 
 
1
  # ============================================================
2
+ # 🧪 backtest_engine.py (V165.0 - GEM-Architect: Coin States & Kill-Switch)
3
  # ============================================================
4
 
5
  import asyncio
 
59
  self.dm = data_manager
60
  self.proc = processor
61
 
62
+ # 🎛️ الكثافة (Density)
63
  self.GRID_DENSITY = 3
64
 
65
  self.INITIAL_CAPITAL = 10.0
66
  self.TRADING_FEES = 0.001
67
  self.MAX_SLOTS = 4
68
 
69
+ # 🎛️ CONTROL PANEL
70
  self.GRID_RANGES = {
71
  'TITAN': np.linspace(0.20, 0.50, self.GRID_DENSITY),
72
  'ORACLE': np.linspace(0.50, 0.80, self.GRID_DENSITY),
73
  'SNIPER': np.linspace(0.30, 0.70, self.GRID_DENSITY),
74
+ 'GOV_SCORE': np.linspace(50.0, 75.0, self.GRID_DENSITY),
75
  }
76
 
77
  self.TARGET_COINS = [
 
86
  ]
87
 
88
  # ✅ DATE SETTINGS
89
+ self.USE_FIXED_DATES = False
90
+ self.LOOKBACK_DAYS = 30
91
  self.force_start_date = "2024-01-01"
92
  self.force_end_date = "2024-02-01"
93
 
94
  if not os.path.exists(CACHE_DIR): os.makedirs(CACHE_DIR)
95
+ print(f"🧪 [Backtest V165.0] Coin States & Kill-Switch System.")
96
 
97
  def set_date_range(self, start_str, end_str):
98
  self.force_start_date = start_str
 
131
  return df.values.tolist()
132
 
133
  # ----------------------------------------------------------------------
134
+ # 🏎️ VECTORIZED INDICATORS (Enhanced for Coin Types)
135
  # ----------------------------------------------------------------------
136
  def _calculate_indicators_vectorized(self, df, timeframe='1m'):
137
  if df.empty: return df
138
  cols = ['close', 'high', 'low', 'volume', 'open']
139
  for c in cols: df[c] = df[c].astype(np.float64)
140
 
141
+ # EMAs
142
  df['ema9'] = df['close'].ewm(span=9, adjust=False).mean()
143
  df['ema20'] = df['close'].ewm(span=20, adjust=False).mean()
144
  df['ema21'] = df['close'].ewm(span=21, adjust=False).mean()
 
159
  macd = ta.macd(df['close'])
160
  if macd is not None:
161
  df['MACD'] = macd.iloc[:, 0].fillna(0)
162
+ df['MACD_s'] = macd.iloc[:, 2].fillna(0)
163
  df['MACD_h'] = macd.iloc[:, 1].fillna(0)
164
  else: df['MACD'] = 0; df['MACD_h'] = 0; df['MACD_s'] = 0
165
 
166
+ # ✅ Essential for Market Kill-Switch & Trash Filter
167
  df['ADX'] = ta.adx(df['high'], df['low'], df['close'], length=14).iloc[:, 0].fillna(0)
168
+ df['CHOP'] = ta.chop(df['high'], df['low'], df['close'], length=14).fillna(50)
169
+
170
  df['CCI'] = ta.cci(df['high'], df['low'], df['close'], length=20).fillna(0)
171
  df['MFI'] = ta.mfi(df['high'], df['low'], df['close'], df['volume'], length=14).fillna(50)
 
172
  vwap = ta.vwap(df['high'], df['low'], df['close'], df['volume'])
173
  df['vwap'] = vwap.fillna(df['close']) if vwap is not None else df['close']
174
 
175
  c = df['close'].values
176
+ # Distances
177
+ df['dist_ema50'] = (df['ema50'] - c) / df['ema50']
178
  df['dist_upper'] = (df['upper_bb'] - c) / c
179
 
180
+ # Features
181
  df['EMA_9_dist'] = (c / df['ema9'].values) - 1
182
  df['EMA_21_dist'] = (c / df['ema21'].values) - 1
183
  df['EMA_50_dist'] = (c / df['ema50'].values) - 1
184
  df['EMA_200_dist'] = (c / df['ema200'].values) - 1
185
  df['VWAP_dist'] = (c / df['vwap'].values) - 1
186
+ df['ATR_pct'] = (df['ATR'] / (c + 1e-9)) * 100 # In Percent
187
 
188
  if timeframe == '1d': df['Trend_Strong'] = np.where(df['ADX'] > 25, 1.0, 0.0)
189
 
190
  df['vol_z'] = _z_roll_np(df['volume'].values, 20)
191
+ # Relative Volume (Important for Explosive State)
192
  df['rel_vol'] = df['volume'] / (df['volume'].rolling(50).mean() + 1e-9)
193
  df['log_ret'] = np.concatenate([[0], np.diff(np.log(c + 1e-9))])
194
 
 
240
  map_1h = get_map('1h'); map_15m = get_map('15m')
241
 
242
  # ============================================================
243
+ # 🛡️ 1. MARKET KILL-SWITCH (Proxy based on Local Coin Data)
244
+ # ============================================================
245
+ # If the coin itself is dead/choppy, we assume the market for it is dead.
246
+ # Logic: High Chop + Low Volatility + Low ADX = DEAD
247
+ h1_chop = numpy_htf['1h']['CHOP'][map_1h]
248
+ h1_adx = numpy_htf['1h']['ADX'][map_1h]
249
+ h1_atr_pct = numpy_htf['1h']['ATR_pct'][map_1h]
250
+
251
+ # DEAD Condition:
252
+ # (Chop > 61.8) OR (ATR% < 0.3 AND ADX < 20)
253
+ is_market_dead = (h1_chop > 61.8) | ((h1_atr_pct < 0.3) & (h1_adx < 20))
254
+ market_status = np.where(is_market_dead, 0, 1) # 0 = DEAD, 1 = OK
255
+
256
  # ============================================================
257
+ # 💎 2. COIN STATES CLASSIFICATION (Vectorized)
258
+ # ============================================================
259
+ # States: 0=TRASH, 1=ACCUMULATION, 2=SAFE_TREND, 3=EXPLOSIVE
260
+
261
  h1_rsi = numpy_htf['1h']['RSI'][map_1h]
262
  h1_close = numpy_htf['1h']['close'][map_1h]
263
+ h1_bbw = numpy_htf['1h']['bb_width'][map_1h]
264
+ h1_upper = numpy_htf['1h']['upper_bb'][map_1h]
265
  h1_ema20 = numpy_htf['1h']['ema20'][map_1h]
266
  h1_ema50 = numpy_htf['1h']['ema50'][map_1h]
267
  h1_ema200 = numpy_htf['1h']['ema200'][map_1h]
268
+ h1_rel_vol = numpy_htf['1h']['rel_vol'][map_1h]
269
+
270
+ # Initialize as TRASH (0)
271
+ coin_state = np.zeros(len(arr_ts_1m), dtype=np.int8)
272
+
273
+ # A. TRASH (0) - Defined implicitly by what is NOT the others,
274
+ # plus explicit check for very low liquidity
275
+ is_trash_vol = (h1_rel_vol < 0.5) | (h1_atr_pct < 0.2)
276
 
277
+ # B. ACCUMULATION (1)
278
+ # Squeeze (Low BBW) + RSI Neutral
279
+ mask_acc = (h1_bbw < 0.15) & (h1_rsi >= 40) & (h1_rsi <= 60)
 
280
 
281
+ # C. SAFE_TREND (2)
282
+ # ADX Strong + EMA Stacked + Healthy RSI
283
+ mask_safe = (h1_adx > 25) & (h1_ema20 > h1_ema50) & (h1_ema50 > h1_ema200) & (h1_rsi > 50) & (h1_rsi < 75)
284
 
285
+ # D. EXPLOSIVE (3)
286
+ # High RSI + Breakout + Volume Spike
287
+ mask_exp = (h1_rsi > 65) & (h1_close > h1_upper) & (h1_rel_vol > 1.5)
288
+
289
+ # Apply Priorities (Explosive > Safe > Acc > Trash)
290
+ coin_state[mask_acc] = 1
291
+ coin_state[mask_safe] = 2
292
+ coin_state[mask_exp] = 3
293
+ coin_state[is_trash_vol] = 0 # Force trash if liquidity is bad
294
 
 
 
 
295
  # ============================================================
296
+ # ✅ GOVERNANCE PROXY (Vectorized Scoring)
297
  # ============================================================
 
298
  gov_points = np.zeros(len(arr_ts_1m), dtype=np.float32)
299
 
 
 
300
  m15_ema9 = numpy_htf['15m']['ema9'][map_15m]
301
  m15_ema21 = numpy_htf['15m']['ema21'][map_15m]
302
  m15_ema50 = numpy_htf['15m']['ema50'][map_15m]
 
306
  gov_points += np.where(m15_ema21 > m15_ema50, 10.0, 0.0)
307
  gov_points += np.where(m15_close > numpy_htf['15m']['ema200'][map_15m], 5.0, 0.0)
308
 
 
309
  m15_rsi = numpy_htf['15m']['RSI'][map_15m]
310
  m15_macd = numpy_htf['15m']['MACD'][map_15m]
311
  m15_macd_s = numpy_htf['15m']['MACD_s'][map_15m]
 
313
  gov_points += np.where((m15_rsi > 45) & (m15_rsi < 70), 15.0, 0.0)
314
  gov_points += np.where(m15_macd > m15_macd_s, 15.0, 0.0)
315
 
 
316
  m15_bbw = numpy_htf['15m']['bb_width'][map_15m]
317
+ gov_points += np.where((m15_bbw > 0.02) & (m15_bbw < 0.15), 10.0, 0.0)
318
  gov_points += np.where(numpy_htf['15m']['rel_vol'][map_15m] > 1.0, 10.0, 0.0)
 
 
 
319
  gov_points += np.where(m15_close > numpy_htf['15m']['vwap'][map_15m], 20.0, 0.0)
320
 
 
321
  gov_scores_final = np.clip(gov_points, 0, 100)
322
 
323
  # ============================================================
 
331
 
332
  global_titan_scores = np.full(len(arr_ts_1m), 0.5, dtype=np.float32)
333
  if titan_model:
 
334
  titan_cols = [
335
  '5m_open', '5m_high', '5m_low', '5m_close', '5m_volume', '5m_RSI', '5m_MACD', '5m_MACD_h',
336
  '5m_CCI', '5m_ADX', '5m_EMA_9_dist', '5m_EMA_21_dist', '5m_EMA_50_dist', '5m_EMA_200_dist',
 
342
  '1d_RSI', '1d_EMA_200_dist', '1d_Trend_Strong'
343
  ]
344
  try:
 
345
  def get_map_local(tf):
346
  if tf not in numpy_htf: return np.zeros(len(arr_ts_1m), dtype=int)
347
  return np.clip(np.searchsorted(numpy_htf[tf]['timestamp'], arr_ts_1m), 0, len(numpy_htf[tf]['timestamp']) - 1)
 
363
  global_oracle_scores = np.full(len(arr_ts_1m), 0.5, dtype=np.float32)
364
  if oracle_dir:
365
  try:
 
366
  map_4h = locals().get('map_4h', get_map('4h'))
367
  o_vecs = []
368
  for col in oracle_cols:
 
392
  global_sniper_scores = _revive_score_distribution(np.mean(preds, axis=0))
393
  except: pass
394
 
395
+ # Filter: Must NOT be TRASH, Must be Market OK, Must have decent scores
396
+ is_candidate_mask = (coin_state > 0) & (market_status == 1) & \
397
+ ((global_titan_scores > 0.6) & (global_oracle_scores > 0.6))
 
 
 
398
 
399
  candidate_indices = np.where(is_candidate_mask)[0]
400
  end_limit = len(arr_ts_1m) - 60
401
  candidate_indices = candidate_indices[candidate_indices < end_limit]
402
  candidate_indices = candidate_indices[candidate_indices >= 500]
403
 
404
+ print(f" 🌪️ Final List: {len(candidate_indices)} candidates (Trash/Dead filtered).", flush=True)
405
 
406
  ai_results = pd.DataFrame({
407
  'timestamp': arr_ts_1m[candidate_indices],
 
410
  'real_titan': global_titan_scores[candidate_indices],
411
  'oracle_conf': global_oracle_scores[candidate_indices],
412
  'sniper_score': global_sniper_scores[candidate_indices],
413
+ 'gov_score': gov_scores_final[candidate_indices],
414
+ 'coin_state': coin_state[candidate_indices], # 1=ACC, 2=SAFE, 3=EXP
415
+ 'market_ok': market_status[candidate_indices]
416
  })
417
 
418
  dt = time.time() - t0
 
443
 
444
  @staticmethod
445
  def _worker_optimize(combinations_batch, scores_files, initial_capital, fees_pct, max_slots):
446
+ """🚀 HYPER-SPEED JUMP LOGIC (Filtering TRASH + DEAD MARKETS)"""
447
  print(f" ⏳ [System] Loading {len(scores_files)} datasets...", flush=True)
448
  data = []
449
  for f in scores_files:
 
461
  oracle = df['oracle_conf'].values
462
  sniper = df['sniper_score'].values
463
  titan = df['real_titan'].values
464
+ gov_s = df['gov_score'].values
465
+ c_state = df['coin_state'].values # 0=TRASH, 1=ACC, 2=SAFE, 3=EXP
466
+ m_ok = df['market_ok'].values # 1=OK, 0=DEAD
467
 
468
  N = len(ts)
469
  print(f" 🚀 [System] Testing {len(combinations_batch)} configs on {N} candidates...", flush=True)
470
 
471
  res = []
472
  for cfg in combinations_batch:
473
+ # ✅ Updated Entry Mask: Kill-Switch + Coin State
474
+ # Must be Market OK (1) AND Coin State > 0 (Not TRASH)
475
+ entry_mask = (m_ok == 1) & \
476
+ (c_state > 0) & \
477
  (gov_s >= cfg['GOV_SCORE']) & \
478
  (oracle >= cfg['ORACLE']) & \
479
  (sniper >= cfg['SNIPER']) & \
 
481
 
482
  valid_entry_indices = np.where(entry_mask)[0]
483
 
484
+ pos = {}
 
485
  bal = float(initial_capital)
486
  alloc = 0.0
487
  log = []
 
489
  for i in range(N):
490
  s = sym_id[i]; p = float(close[i])
491
 
492
+ # A. Check Exits
493
  if s in pos:
494
  entry_p, size_val = pos[s]
495
  pnl = (p - entry_p) / entry_p
496
 
 
497
  if (pnl > 0.04) or (pnl < -0.02):
498
  realized = pnl - (fees_pct * 2)
499
  bal += size_val * (1.0 + realized)
 
553
 
554
  mapped_config = {
555
  'w_titan': best['config']['TITAN'],
556
+ 'w_struct': 0.3,
557
+ 'thresh': 50.0,
558
  'oracle_thresh': best['config']['ORACLE'],
559
  'sniper_thresh': best['config']['SNIPER'],
560
  'gov_thresh': best['config']['GOV_SCORE'],
561
+ 'hydra_thresh': 0.85,
562
+ 'legacy_thresh': 0.95
563
  }
564
 
565
  # Diagnosis
 
598
  hub = AdaptiveHub(r2); await hub.initialize()
599
  opt = HeavyDutyBacktester(dm, proc)
600
 
601
+ scenarios = [{"regime": "RANGE"}]
 
 
 
 
602
 
603
  for s in scenarios:
 
604
  best_cfg, best_stats = await opt.run_optimization(s["regime"])
605
  if best_cfg: hub.submit_challenger(s["regime"], best_cfg, best_stats)
606