Spaces:
Sleeping
Sleeping
Update backtest_engine.py
Browse files- backtest_engine.py +84 -76
backtest_engine.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
# ============================================================
|
| 2 |
-
# 🧪 backtest_engine.py (
|
| 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
|
| 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 |
}
|
| 76 |
|
| 77 |
self.TARGET_COINS = [
|
|
@@ -86,13 +86,13 @@ class HeavyDutyBacktester:
|
|
| 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
|
| 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
|
| 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 |
-
#
|
| 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)
|
| 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
|
| 175 |
-
df['dist_ema50'] = (df['ema50'] - c) / df['ema50']
|
| 176 |
df['dist_upper'] = (df['upper_bb'] - c) / c
|
| 177 |
|
| 178 |
-
# Features
|
| 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 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
# ============================================================
|
| 242 |
-
#
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 249 |
-
|
| 250 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
|
| 252 |
-
#
|
| 253 |
-
#
|
| 254 |
-
|
| 255 |
-
mask_safe_bottom = (h1_rsi < 45) & (h1_close <= h1_lower_bb * 1.05) & (dist_from_ema50 > 0.015)
|
| 256 |
|
| 257 |
-
#
|
| 258 |
-
#
|
| 259 |
-
|
| 260 |
|
| 261 |
-
#
|
| 262 |
-
#
|
| 263 |
-
|
| 264 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
|
| 266 |
-
# Combine Masks (Any Valid L1)
|
| 267 |
-
valid_l1_mask = mask_safe_bottom | mask_acc_squeeze | mask_mom_launch
|
| 268 |
-
|
| 269 |
# ============================================================
|
| 270 |
-
# ✅
|
| 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)
|
| 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 |
-
#
|
| 382 |
-
|
| 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
|
| 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],
|
| 403 |
-
'
|
|
|
|
| 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 (
|
| 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
|
| 453 |
-
|
|
|
|
| 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:
|
| 461 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 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
|
| 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,
|
| 544 |
-
'thresh': 50.0,
|
| 545 |
'oracle_thresh': best['config']['ORACLE'],
|
| 546 |
'sniper_thresh': best['config']['SNIPER'],
|
| 547 |
'gov_thresh': best['config']['GOV_SCORE'],
|
| 548 |
-
'hydra_thresh': 0.85,
|
| 549 |
-
'legacy_thresh': 0.95
|
| 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 |
-
|
| 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 |
|