Spaces:
Paused
Paused
Update backtest_engine.py
Browse files- backtest_engine.py +89 -40
backtest_engine.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
# ============================================================
|
| 2 |
-
# π§ͺ backtest_engine.py (
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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'
|
| 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
|
| 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]
|
| 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 (
|
| 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 |
-
|
| 497 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 569 |
-
|
| 570 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 571 |
combinations = []
|
| 572 |
-
|
|
|
|
| 573 |
combinations.append({
|
| 574 |
-
'w_titan':
|
| 575 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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"},
|