Spaces:
Paused
Paused
Update backtest_engine.py
Browse files- backtest_engine.py +78 -27
backtest_engine.py
CHANGED
|
@@ -399,7 +399,9 @@ class HeavyDutyBacktester:
|
|
| 399 |
for sym in self.TARGET_COINS:
|
| 400 |
c = await self._fetch_all_data_fast(sym, ms_s, ms_e)
|
| 401 |
if c: await self._process_data_in_memory(sym, c, ms_s, ms_e)
|
| 402 |
-
|
|
|
|
|
|
|
| 403 |
@staticmethod
|
| 404 |
def _worker_optimize(combinations_batch, scores_files, initial_capital, fees_pct, max_slots):
|
| 405 |
print(f" β³ [System] Loading {len(scores_files)} datasets...", flush=True)
|
|
@@ -410,7 +412,7 @@ class HeavyDutyBacktester:
|
|
| 410 |
if not data: return []
|
| 411 |
df = pd.concat(data).sort_values('timestamp')
|
| 412 |
|
| 413 |
-
# Arrays
|
| 414 |
ts = df['timestamp'].values; close = df['close'].values.astype(float)
|
| 415 |
sym = df['symbol'].values; sym_map = {s:i for i,s in enumerate(np.unique(sym))}
|
| 416 |
sym_id = np.array([sym_map[s] for s in sym])
|
|
@@ -424,65 +426,113 @@ class HeavyDutyBacktester:
|
|
| 424 |
|
| 425 |
res = []
|
| 426 |
for cfg in combinations_batch:
|
| 427 |
-
pos = {}; log = []
|
| 428 |
bal = initial_capital; alloc = 0.0
|
| 429 |
|
|
|
|
| 430 |
mask = (l1 >= cfg['l1_thresh']) & (oracle >= cfg['oracle_thresh']) & (sniper >= cfg['sniper_thresh'])
|
| 431 |
|
| 432 |
for i in range(N):
|
| 433 |
s = sym_id[i]; p = close[i]
|
| 434 |
|
| 435 |
-
#
|
| 436 |
if s in pos:
|
| 437 |
-
|
|
|
|
|
|
|
|
|
|
| 438 |
crash = (h_r > cfg['hydra_thresh'])
|
| 439 |
-
pnl = (p -
|
| 440 |
|
|
|
|
| 441 |
if crash or pnl > 0.04 or pnl < -0.02:
|
| 442 |
-
|
|
|
|
|
|
|
|
|
|
| 443 |
alloc -= pos[s][2]
|
| 444 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 445 |
del pos[s]
|
| 446 |
|
| 447 |
-
#
|
| 448 |
if len(pos) < max_slots and mask[i]:
|
| 449 |
if s not in pos and bal >= 5.0:
|
| 450 |
-
size = min(10.0, bal * 0.98)
|
| 451 |
-
|
|
|
|
| 452 |
bal -= size; alloc += size
|
| 453 |
|
| 454 |
-
#
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 458 |
res.append({
|
| 459 |
-
'config': cfg,
|
| 460 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 461 |
})
|
|
|
|
| 462 |
return res
|
| 463 |
|
|
|
|
|
|
|
|
|
|
| 464 |
async def run_optimization(self, target_regime="RANGE"):
|
| 465 |
await self.generate_truth_data()
|
| 466 |
|
| 467 |
-
# Grid
|
| 468 |
-
oracle_r = np.linspace(0.
|
| 469 |
-
|
|
|
|
|
|
|
| 470 |
|
| 471 |
combos = []
|
| 472 |
for o, s, h, l1 in itertools.product(oracle_r, sniper_r, hydra_r, l1_r):
|
| 473 |
combos.append({
|
| 474 |
'w_titan': 0.4, 'w_struct': 0.3,
|
| 475 |
-
'l1_thresh': l1, 'oracle_thresh': o, 'sniper_thresh': s,
|
|
|
|
| 476 |
})
|
| 477 |
|
| 478 |
files = glob.glob(os.path.join(CACHE_DIR, "*.pkl"))
|
| 479 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 480 |
|
| 481 |
-
|
| 482 |
-
if not best_res:
|
| 483 |
print("β οΈ [Warning] No trades generated in any config.")
|
| 484 |
return None, {'net_profit': 0.0, 'win_rate': 0.0}
|
| 485 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 486 |
print("\n" + "="*60)
|
| 487 |
print(f"π CHAMPION REPORT [{target_regime}]:")
|
| 488 |
print(f" π° Final Balance: ${best['final_balance']:,.2f}")
|
|
@@ -490,17 +540,18 @@ class HeavyDutyBacktester:
|
|
| 490 |
print("-" * 60)
|
| 491 |
print(f" π Total Trades: {best['total_trades']}")
|
| 492 |
print(f" π Win Rate: {best['win_rate']:.1f}%")
|
| 493 |
-
print(f" π Max Drawdown: {best['max_drawdown']:.1f}%")
|
| 494 |
print("-" * 60)
|
| 495 |
print(f" π§ CONSENSUS ANALYTICS:")
|
| 496 |
-
print(f" π€ Model Agreement Rate: {best['consensus_agreement_rate']:.1f}%")
|
| 497 |
print(f" π High-Consensus Win Rate: {best['high_consensus_win_rate']:.1f}%")
|
| 498 |
print(f" π High-Consensus Avg PnL: {best['high_consensus_avg_pnl']:.2f}%")
|
| 499 |
print("-" * 60)
|
| 500 |
print(f" βοΈ Oracle={best['config']['oracle_thresh']:.2f} | Sniper={best['config']['sniper_thresh']:.2f} | Hydra={best['config']['hydra_thresh']:.2f}")
|
| 501 |
-
print(f" βοΈ Weights: Titan={best['config']['w_titan']:.2f} | Patterns={best['config']['w_struct']:.2f} | L1={best['config']['l1_thresh']}")
|
| 502 |
print("="*60)
|
|
|
|
| 503 |
return best['config'], best
|
|
|
|
| 504 |
async def run_strategic_optimization_task():
|
| 505 |
print("\nπ§ͺ [STRATEGIC BACKTEST] Full Spectrum Mode...")
|
| 506 |
r2 = R2Service(); dm = DataManager(None, None, r2); proc = MLProcessor(dm)
|
|
|
|
| 399 |
for sym in self.TARGET_COINS:
|
| 400 |
c = await self._fetch_all_data_fast(sym, ms_s, ms_e)
|
| 401 |
if c: await self._process_data_in_memory(sym, c, ms_s, ms_e)
|
| 402 |
+
# ============================================================
|
| 403 |
+
# π§ FIX: Updated Worker with Consensus Metrics
|
| 404 |
+
# ============================================================
|
| 405 |
@staticmethod
|
| 406 |
def _worker_optimize(combinations_batch, scores_files, initial_capital, fees_pct, max_slots):
|
| 407 |
print(f" β³ [System] Loading {len(scores_files)} datasets...", flush=True)
|
|
|
|
| 412 |
if not data: return []
|
| 413 |
df = pd.concat(data).sort_values('timestamp')
|
| 414 |
|
| 415 |
+
# Arrays extraction
|
| 416 |
ts = df['timestamp'].values; close = df['close'].values.astype(float)
|
| 417 |
sym = df['symbol'].values; sym_map = {s:i for i,s in enumerate(np.unique(sym))}
|
| 418 |
sym_id = np.array([sym_map[s] for s in sym])
|
|
|
|
| 426 |
|
| 427 |
res = []
|
| 428 |
for cfg in combinations_batch:
|
| 429 |
+
pos = {}; log = [] # log stores: (pnl, was_consensus)
|
| 430 |
bal = initial_capital; alloc = 0.0
|
| 431 |
|
| 432 |
+
# Entry Logic Mask
|
| 433 |
mask = (l1 >= cfg['l1_thresh']) & (oracle >= cfg['oracle_thresh']) & (sniper >= cfg['sniper_thresh'])
|
| 434 |
|
| 435 |
for i in range(N):
|
| 436 |
s = sym_id[i]; p = close[i]
|
| 437 |
|
| 438 |
+
# --- EXIT LOGIC ---
|
| 439 |
if s in pos:
|
| 440 |
+
entry_price = pos[s][0]
|
| 441 |
+
h_r = pos[s][1]
|
| 442 |
+
entry_titan_score = pos[s][3] # Stored Titan Score
|
| 443 |
+
|
| 444 |
crash = (h_r > cfg['hydra_thresh'])
|
| 445 |
+
pnl = (p - entry_price) / entry_price
|
| 446 |
|
| 447 |
+
# Simple SL/TP or Hydra Exit
|
| 448 |
if crash or pnl > 0.04 or pnl < -0.02:
|
| 449 |
+
# Fee impact (entry + exit)
|
| 450 |
+
realized_pnl = pnl - (fees_pct * 2)
|
| 451 |
+
|
| 452 |
+
bal += pos[s][2] * (1 + realized_pnl)
|
| 453 |
alloc -= pos[s][2]
|
| 454 |
+
|
| 455 |
+
# Check consensus for this trade (Did Titan agree?)
|
| 456 |
+
is_consensus = (entry_titan_score > 0.55) # Threshold for agreement
|
| 457 |
+
log.append({'pnl': realized_pnl, 'consensus': is_consensus})
|
| 458 |
+
|
| 459 |
del pos[s]
|
| 460 |
|
| 461 |
+
# --- ENTRY LOGIC ---
|
| 462 |
if len(pos) < max_slots and mask[i]:
|
| 463 |
if s not in pos and bal >= 5.0:
|
| 464 |
+
size = min(10.0, bal * 0.98) # Fixed size per slot mostly
|
| 465 |
+
# Store: (Price, HydraRisk, Size, TitanScore)
|
| 466 |
+
pos[s] = (p, hydra[i], size, titan[i])
|
| 467 |
bal -= size; alloc += size
|
| 468 |
|
| 469 |
+
# --- METRICS CALCULATION ---
|
| 470 |
+
final_bal = bal + alloc
|
| 471 |
+
net_profit = final_bal - initial_capital
|
| 472 |
+
total_trades = len(log)
|
| 473 |
+
wins = sum(1 for x in log if x['pnl'] > 0)
|
| 474 |
+
win_rate = (wins / total_trades * 100) if total_trades > 0 else 0.0
|
| 475 |
+
|
| 476 |
+
# Consensus Metrics Calculation
|
| 477 |
+
consensus_trades = [x for x in log if x['consensus']]
|
| 478 |
+
num_con = len(consensus_trades)
|
| 479 |
+
|
| 480 |
+
agreement_rate = (num_con / total_trades * 100) if total_trades > 0 else 0.0
|
| 481 |
+
con_wins = sum(1 for x in consensus_trades if x['pnl'] > 0)
|
| 482 |
+
con_win_rate = (con_wins / num_con * 100) if num_con > 0 else 0.0
|
| 483 |
+
con_avg_pnl = (sum(x['pnl'] for x in consensus_trades) / num_con * 100) if num_con > 0 else 0.0
|
| 484 |
+
|
| 485 |
res.append({
|
| 486 |
+
'config': cfg,
|
| 487 |
+
'final_balance': final_bal, # β
Added
|
| 488 |
+
'net_profit': net_profit,
|
| 489 |
+
'total_trades': total_trades,
|
| 490 |
+
'win_rate': win_rate,
|
| 491 |
+
'max_drawdown': 0.0, # Simplified for speed
|
| 492 |
+
'consensus_agreement_rate': agreement_rate, # β
Added
|
| 493 |
+
'high_consensus_win_rate': con_win_rate, # β
Added
|
| 494 |
+
'high_consensus_avg_pnl': con_avg_pnl # β
Added
|
| 495 |
})
|
| 496 |
+
|
| 497 |
return res
|
| 498 |
|
| 499 |
+
# ============================================================
|
| 500 |
+
# π§ FIX: Run Optimization with Safe Sorting
|
| 501 |
+
# ============================================================
|
| 502 |
async def run_optimization(self, target_regime="RANGE"):
|
| 503 |
await self.generate_truth_data()
|
| 504 |
|
| 505 |
+
# Grid Setup
|
| 506 |
+
oracle_r = np.linspace(0.5, 0.8, 3)
|
| 507 |
+
sniper_r = np.linspace(0.3, 0.6, 3)
|
| 508 |
+
hydra_r = [0.85, 0.95]
|
| 509 |
+
l1_r = [5.0]
|
| 510 |
|
| 511 |
combos = []
|
| 512 |
for o, s, h, l1 in itertools.product(oracle_r, sniper_r, hydra_r, l1_r):
|
| 513 |
combos.append({
|
| 514 |
'w_titan': 0.4, 'w_struct': 0.3,
|
| 515 |
+
'l1_thresh': l1, 'oracle_thresh': o, 'sniper_thresh': s,
|
| 516 |
+
'hydra_thresh': h, 'legacy_thresh': 0.95
|
| 517 |
})
|
| 518 |
|
| 519 |
files = glob.glob(os.path.join(CACHE_DIR, "*.pkl"))
|
| 520 |
+
if not files:
|
| 521 |
+
print("β οΈ [Warning] No data files found for backtest.")
|
| 522 |
+
return None, {'net_profit': 0.0}
|
| 523 |
+
|
| 524 |
+
# Run Worker
|
| 525 |
+
results_list = self._worker_optimize(combos, files, self.INITIAL_CAPITAL, self.TRADING_FEES, self.MAX_SLOTS)
|
| 526 |
|
| 527 |
+
if not results_list:
|
|
|
|
| 528 |
print("β οΈ [Warning] No trades generated in any config.")
|
| 529 |
return None, {'net_profit': 0.0, 'win_rate': 0.0}
|
| 530 |
|
| 531 |
+
# β
FIX: Sort and select 'best'
|
| 532 |
+
# Sort by Net Profit descending
|
| 533 |
+
results_list.sort(key=lambda x: x['net_profit'], reverse=True)
|
| 534 |
+
best = results_list[0] # The Champion
|
| 535 |
+
|
| 536 |
print("\n" + "="*60)
|
| 537 |
print(f"π CHAMPION REPORT [{target_regime}]:")
|
| 538 |
print(f" π° Final Balance: ${best['final_balance']:,.2f}")
|
|
|
|
| 540 |
print("-" * 60)
|
| 541 |
print(f" π Total Trades: {best['total_trades']}")
|
| 542 |
print(f" π Win Rate: {best['win_rate']:.1f}%")
|
| 543 |
+
print(f" π Max Drawdown: {best['max_drawdown']:.1f}% (Approx)")
|
| 544 |
print("-" * 60)
|
| 545 |
print(f" π§ CONSENSUS ANALYTICS:")
|
| 546 |
+
print(f" π€ Model Agreement Rate: {best['consensus_agreement_rate']:.1f}% (Titan Agreed)")
|
| 547 |
print(f" π High-Consensus Win Rate: {best['high_consensus_win_rate']:.1f}%")
|
| 548 |
print(f" π High-Consensus Avg PnL: {best['high_consensus_avg_pnl']:.2f}%")
|
| 549 |
print("-" * 60)
|
| 550 |
print(f" βοΈ Oracle={best['config']['oracle_thresh']:.2f} | Sniper={best['config']['sniper_thresh']:.2f} | Hydra={best['config']['hydra_thresh']:.2f}")
|
|
|
|
| 551 |
print("="*60)
|
| 552 |
+
|
| 553 |
return best['config'], best
|
| 554 |
+
|
| 555 |
async def run_strategic_optimization_task():
|
| 556 |
print("\nπ§ͺ [STRATEGIC BACKTEST] Full Spectrum Mode...")
|
| 557 |
r2 = R2Service(); dm = DataManager(None, None, r2); proc = MLProcessor(dm)
|