Riy777 commited on
Commit
f080b76
·
verified ·
1 Parent(s): 8894213

Update backtest_engine.py

Browse files
Files changed (1) hide show
  1. backtest_engine.py +175 -217
backtest_engine.py CHANGED
@@ -1,10 +1,10 @@
1
  # ============================================================
2
- # 🧪 backtest_engine.py (V42.1 - GEM-Architect: Exact Threshold Mapping)
3
  # ============================================================
4
- # التحديثات:
5
- # 1. مطابقة تامة لمتغيرات processor.py (Hydra Crash/Giveback, Legacy V2/V3).
6
- # 2. توسيع الشبكة لتشمل هذه المتغيرات الدقيقة.
7
- # 3. تقرير يفصل أي "رأس" من رؤوس الحراس كان الأنشط.
8
  # ============================================================
9
 
10
  import asyncio
@@ -13,7 +13,8 @@ import numpy as np
13
  import time
14
  import logging
15
  import itertools
16
- from datetime import datetime
 
17
  from typing import Dict, Any, List
18
 
19
  from ml_engine.processor import MLProcessor, SystemLimits
@@ -23,34 +24,13 @@ from learning_hub.adaptive_hub import StrategyDNA
23
  # كتم الضوضاء
24
  logging.getLogger('ml_engine.patterns').setLevel(logging.WARNING)
25
 
26
- class VirtualPortfolio:
27
- def __init__(self, initial_capital=1000.0):
28
- self.capital = initial_capital
29
- self.active_trades = {}
30
- self.stats = {
31
- "max_win_usd": 0.0, "max_loss_usd": 0.0,
32
- "max_drawdown_pct": 0.0, "max_runup_pct": 0.0
33
- }
34
- # تفصيل أدق للحراس
35
- self.guardian_log = {
36
- 'hydra_crash': 0, 'hydra_giveback': 0, 'hydra_stag': 0,
37
- 'legacy_v2': 0, 'legacy_v3': 0,
38
- 'tp': 0, 'sl': 0
39
- }
40
- self.MAX_SLOTS_MAP = {'BULL': 6, 'BEAR': 3, 'RANGE': 5, 'DEAD': 2}
41
-
42
- def can_open_trade(self, regime):
43
- max_slots = self.MAX_SLOTS_MAP.get(regime, 4)
44
- return len(self.active_trades) < max_slots
45
-
46
- def calculate_size(self, confidence, regime):
47
- return self.capital * 0.10
48
 
49
  class BacktestSimulator:
50
  def __init__(self, data_manager, processor):
51
  self.dm = data_manager
52
  self.proc = processor
53
- self.history_cache = {}
54
  self.DAYS_TO_FETCH = 7
55
  self.CHUNK_LIMIT = 1000
56
 
@@ -61,18 +41,29 @@ class BacktestSimulator:
61
  'XLM/USDT', 'TRX/USDT', 'LTC/USDT'
62
  ]
63
 
64
- print("🧪 [Backtest Engine V42.1] Exact Threshold Grid Initialized.")
 
 
 
65
 
66
  # ==========================================================================
67
- # 1. Data Loading
68
  # ==========================================================================
69
  async def fetch_deep_history_1m(self):
70
- print(f"\n⏳ [Data] Loading {len(self.TARGET_COINS)} coins ({self.DAYS_TO_FETCH} days)...")
71
  end_time_ms = int(time.time() * 1000)
72
  start_time_ms = end_time_ms - (self.DAYS_TO_FETCH * 24 * 60 * 60 * 1000)
73
 
74
- loaded_count = 0
75
  for sym in self.TARGET_COINS:
 
 
 
 
 
 
 
 
 
76
  print(f" ⬇️ {sym:<10}", end="", flush=True)
77
  all_candles = []
78
  current_since = start_time_ms
@@ -95,22 +86,23 @@ class BacktestSimulator:
95
  df['datetime'] = pd.to_datetime(df['timestamp'], unit='ms')
96
  df = df.set_index('datetime').sort_index()
97
 
98
- mask = (df.index >= pd.to_datetime(start_time_ms, unit='ms'))
99
- self.history_cache[sym] = df.loc[mask]
100
- loaded_count += 1
101
- print(f" ✅ ({len(self.history_cache[sym])})")
 
 
 
102
  else:
103
  print(" ⚠️ No Data")
104
 
105
- print(f"✅ Data Load Complete. Cached {loaded_count}/{len(self.TARGET_COINS)} coins.")
106
 
107
  # ==========================================================================
108
- # 2. Helpers
109
  # ==========================================================================
110
- def get_market_snapshot(self, symbol, end_idx):
111
  try:
112
- df_full = self.history_cache.get(symbol)
113
- if df_full is None: return None
114
  LOOKBACK_WINDOW = 6000
115
  start_pos = max(0, end_idx - LOOKBACK_WINDOW)
116
  slice_1m = df_full.iloc[start_pos : end_idx + 1].copy()
@@ -131,7 +123,7 @@ class BacktestSimulator:
131
  # ==========================================================================
132
  # 3. Process Logic
133
  # ==========================================================================
134
- async def process_market_layers(self, symbol, snapshot, current_price, weights, l1_threshold):
135
  # L2
136
  titan_score = 0.5
137
  if self.proc.titan:
@@ -188,212 +180,179 @@ class BacktestSimulator:
188
  return None
189
 
190
  # ==========================================================================
191
- # 4. Simulation Loop (Exact Threshold Injection)
192
  # ==========================================================================
193
- async def run_simulation(self, regime_name, weights, l1_thresh, hydra_crash, hydra_giveback, legacy_v2, legacy_v3):
194
- SystemLimits.CURRENT_REGIME = regime_name
195
- self.portfolio = VirtualPortfolio()
196
- trades_log = []
197
-
198
- ref_symbol = list(self.history_cache.keys())[0]
199
- full_index = self.history_cache[ref_symbol].index
 
 
200
  start_idx = 6000
201
  end_idx = len(full_index) - 1
202
  current_idx = start_idx
203
 
 
 
 
 
204
  while current_idx < end_idx:
 
205
  current_time = full_index[current_idx]
206
 
207
- # --- أ. الحراس ---
208
- active_symbols = list(self.portfolio.active_trades.keys())
209
- for sym in active_symbols:
210
- trade = self.portfolio.active_trades[sym]
211
- try:
212
- sym_idx = self.history_cache[sym].index.get_loc(current_time)
213
- current_price = self.history_cache[sym].iloc[sym_idx]['close']
214
- except: continue
215
 
216
- if current_price > trade['highest_price']: trade['highest_price'] = current_price
217
- if current_price < trade.get('lowest_price', trade['entry_price']): trade['lowest_price'] = current_price
218
-
219
- # Max DD
220
- dd = (trade['lowest_price'] - trade['entry_price']) / trade['entry_price']
221
- if dd < self.portfolio.stats['max_drawdown_pct']: self.portfolio.stats['max_drawdown_pct'] = dd
222
-
223
- snapshot = self.get_market_snapshot(sym, sym_idx)
224
- if not snapshot: continue
 
 
 
 
 
225
 
226
- exit_reason = None
227
- trade_ctx = {
228
- 'entry_price': trade['entry_price'],
229
- 'highest_price': trade['highest_price'],
230
- 'time_in_trade_mins': (current_idx - trade['entry_idx'])
231
- }
232
 
233
- # 🛡️ 1. Hydra (Exact Thresholds)
234
- if self.proc.guardian_hydra:
235
- # حقن المتغيرات الدقيقة
236
- SystemLimits.HYDRA_CRASH_THRESH = hydra_crash
237
- SystemLimits.HYDRA_GIVEBACK_THRESH = hydra_giveback
238
- SystemLimits.HYDRA_STAGNATION_THRESH = 0.50 # ثابت حالياً
239
-
240
- hydra_res = self.proc.guardian_hydra.analyze_position(sym, snapshot['1m'], snapshot['5m'], snapshot['15m'], trade_ctx)
241
- if hydra_res['action'] in ['EXIT_HARD', 'EXIT_SOFT']:
242
- exit_reason = f"Hydra_{hydra_res.get('reason','Unknown')}"
243
-
244
- # 🛡️ 2. Legacy (Exact Thresholds)
245
- if not exit_reason and self.proc.guardian_legacy:
246
- # حقن المتغيرات الدقيقة
247
- self.proc.guardian_legacy.configure_thresholds(
248
- v2_panic=legacy_v2,
249
- v3_hard=legacy_v3,
250
- v3_soft=legacy_v3-0.1, # مشتق منطقي
251
- v3_ultra=0.99
252
- )
253
- legacy_res = self.proc.guardian_legacy.analyze_position(
254
- snapshot['1m'], snapshot['5m'], snapshot['15m'], trade['entry_price'], order_book=None
255
- )
256
- if legacy_res['action'] == 'EXIT_HARD':
257
- exit_reason = f"Legacy_{legacy_res.get('reason','Unknown')}"
258
-
259
- if not exit_reason:
260
- if current_price >= trade['tp']: exit_reason = "TP"
261
- elif current_price <= trade['sl']: exit_reason = "SL"
262
 
263
- if exit_reason:
264
- pnl_pct = (current_price - trade['entry_price']) / trade['entry_price']
265
- pnl_usd = trade['size'] * pnl_pct
266
- self.portfolio.capital += (trade['size'] + pnl_usd)
267
- trades_log.append(pnl_pct)
268
-
269
- # تسجيل دقيق للسبب
270
- if 'Crash' in exit_reason: self.portfolio.guardian_log['hydra_crash'] += 1
271
- elif 'Giveback' in exit_reason: self.portfolio.guardian_log['hydra_giveback'] += 1
272
- elif 'V2' in exit_reason: self.portfolio.guardian_log['legacy_v2'] += 1
273
- elif 'V3' in exit_reason: self.portfolio.guardian_log['legacy_v3'] += 1
274
- elif 'TP' in exit_reason: self.portfolio.guardian_log['tp'] += 1
275
- elif 'SL' in exit_reason: self.portfolio.guardian_log['sl'] += 1
276
-
277
- if pnl_usd > self.portfolio.stats['max_win_usd']: self.portfolio.stats['max_win_usd'] = pnl_usd
278
- if pnl_usd < self.portfolio.stats['max_loss_usd']: self.portfolio.stats['max_loss_usd'] = pnl_usd
279
-
280
- del self.portfolio.active_trades[sym]
281
 
282
- # --- ب. المسح ---
283
- if current_idx % 15 == 0 and self.portfolio.can_open_trade(regime_name):
284
- for sym in self.history_cache.keys():
285
- if sym in self.portfolio.active_trades: continue
286
- try:
287
- sym_idx = self.history_cache[sym].index.get_indexer([current_time], method='nearest')[0]
288
- snapshot = self.get_market_snapshot(sym, sym_idx)
289
- if not snapshot: continue
290
- curr_p = snapshot['1m'][-1][4]
291
-
292
- signal = await self.process_market_layers(sym, snapshot, curr_p, weights, l1_thresh)
293
- if signal:
294
- size = self.portfolio.calculate_size(signal['oracle_conf'], regime_name)
295
- self.portfolio.active_trades[sym] = {
296
- 'entry_price': signal['entry_price'], 'tp': signal['tp'], 'sl': signal['sl'],
297
- 'size': size, 'entry_idx': current_idx,
298
- 'highest_price': signal['entry_price'], 'lowest_price': signal['entry_price']
299
- }
300
- self.portfolio.capital -= size
301
- if not self.portfolio.can_open_trade(regime_name): break
302
- except: continue
303
-
304
- current_idx += 1
305
-
306
- wins = len([p for p in trades_log if p > 0])
307
- losses = len(trades_log) - wins
308
- wr = (wins/len(trades_log)*100) if len(trades_log) > 0 else 0.0
309
-
310
- return {
311
- 'final_capital': self.portfolio.capital,
312
- 'win_rate': wr,
313
- 'trades_count': len(trades_log),
314
- 'wins': wins,
315
- 'losses': losses,
316
- 'guards': self.portfolio.guardian_log,
317
- 'stats': self.portfolio.stats
318
- }
319
 
320
  # ==========================================================================
321
- # 5. Master Grid Search (Expanded)
322
  # ==========================================================================
323
  async def optimize_dna(self):
324
  best_dna = {}
325
  regimes = ['RANGE']
326
 
327
- # 1. متغيرات الأوزان
328
  weight_opts = [
329
- {'titan': 0.3, 'patterns': 0.3, 'sniper': 0.3, 'mc': 0.1}, # Balanced
330
- {'titan': 0.5, 'patterns': 0.2, 'sniper': 0.2, 'mc': 0.1}, # Trend
 
331
  ]
332
-
333
- # 2. متغيرات العتبات (Entry)
334
  entry_thresh_opts = [0.55, 0.60]
335
-
336
- # 3. متغيرات Hydra (Crash/Giveback)
337
- hydra_crash_opts = [0.60, 0.70] # هل نجزع عند 60% أم 70%؟
338
  hydra_give_opts = [0.65, 0.75]
 
 
 
 
 
 
 
 
 
 
 
 
339
 
340
- # 4. متغيرات Legacy (V2/V3)
341
- legacy_v2_opts = [0.95, 0.98] # V2 Panic
342
- legacy_v3_opts = [0.95] # V3 Hard (نثبته لتقليل الاحتمالات قليلاً)
343
-
344
- # Grid Size: 2 * 2 * 2 * 2 * 2 * 1 = 32 Combination
345
 
346
- for regime in regimes:
347
- print(f"\n🧪 Optimizing {regime} (Master Grid: 32 combos)...")
348
- # رأس الجدول العريض جداً
349
- header = f"{'W(Ti/Pat/Sn)':<15} | {'En-Th':<5} | {'H-Crsh':<6} | {'H-Giv':<5} | {'L-V2':<4} | {'CAPITAL':<9} | {'W/L':<7} | {'Guards(Cr/Gv/V2/V3)'}"
350
- print("-" * len(header))
351
- print(header)
352
- print("-" * len(header))
353
 
354
- best_score = -9999
355
- best_config = None
356
 
357
- # حلقة التوليفات الكبرى
358
- for w, e_th, h_c, h_g, l_v2, l_v3 in itertools.product(weight_opts, entry_thresh_opts, hydra_crash_opts, hydra_give_opts, legacy_v2_opts, legacy_v3_opts):
359
-
360
- res = await self.run_simulation(regime, w, e_th, h_c, h_g, l_v2, l_v3)
361
-
362
- w_str = f"{w['titan']}/{w['patterns']}/{w['sniper']}"
363
- wl_str = f"{res['wins']}/{res['losses']}"
364
- g = res['guards']
365
- g_str = f"{g['hydra_crash']}/{g['hydra_giveback']}/{g['legacy_v2']}/{g['legacy_v3']}"
366
-
367
- print(f"{w_str:<15} | {e_th:<5} | {h_c:<6} | {h_g:<5} | {l_v2:<4} | ${res['final_capital']:.1f} | {wl_str:<7} | {g_str}")
368
-
369
- if res['final_capital'] > best_score:
370
- best_score = res['final_capital']
371
- best_config = {'w': w, 'e_th': e_th, 'h_c': h_c, 'h_g': h_g, 'l_v2': l_v2, 'l_v3': l_v3, 'res': res}
372
 
373
- if best_config:
374
- best_dna[regime] = {
375
- "model_weights": best_config['w'],
376
- "ob_settings": {"wall_ratio_limit": 0.4, "imbalance_thresh": 0.5},
377
- "filters": {"l1_min_score": best_config['e_th'] * 100, "l3_conf_thresh": 0.65},
378
- # حفظ عتبات الحراس الدقيقة
379
- "guard_settings": {
380
- "hydra_crash": best_config['h_c'],
381
- "hydra_giveback": best_config['h_g'],
382
- "legacy_v2": best_config['l_v2'],
383
- "legacy_v3": best_config['l_v3']
384
- }
385
- }
386
-
387
- s = best_config['res']['stats']
388
- print("-" * len(header))
389
- print(f"🏆 WINNER ({regime}): Profit=${(best_score-1000):.2f} | Max DD: {s['max_drawdown_pct']*100:.2f}%")
390
- print(f" ⚙️ Config: H-Crash={best_config['h_c']}, H-Give={best_config['h_g']}, V2={best_config['l_v2']}")
391
- print("=" * len(header))
392
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
393
  return best_dna
394
 
395
  async def run_strategic_optimization_task():
396
- print("\n🧪 [STRATEGIC BACKTEST V42.1] Exact Threshold Grid...")
397
  from r2 import R2Service
398
  r2 = R2Service()
399
  dm = DataManager(None, None, r2)
@@ -413,7 +372,6 @@ async def run_strategic_optimization_task():
413
  if reg in hub.strategies:
414
  hub.strategies[reg].model_weights.update(data['model_weights'])
415
  hub.strategies[reg].filters = data['filters']
416
- # هنا سنحتاج لتحديث AdaptiveHub لاحقاً ليخزن guard_settings
417
 
418
  await hub._save_state_to_r2()
419
  await dm.close()
 
1
  # ============================================================
2
+ # 🧪 backtest_engine.py (V43.0 - GEM-Architect: Disk-Swapped Grid)
3
  # ============================================================
4
+ # التغييرات الجوهرية (Anti-Crash Architecture):
5
+ # 1. التخزين المؤقت: حفظ البيانات في ملفات محلية بدلاً من RAM.
6
+ # 2. قلب حلقة المعالجة: (Loop Coins -> Loop Strategies) لتقليل التحميل.
7
+ # 3. استهلاك ذاكرة منخفض جداً (عملة واحدة في كل لحظة).
8
  # ============================================================
9
 
10
  import asyncio
 
13
  import time
14
  import logging
15
  import itertools
16
+ import os
17
+ import shutil
18
  from typing import Dict, Any, List
19
 
20
  from ml_engine.processor import MLProcessor, SystemLimits
 
24
  # كتم الضوضاء
25
  logging.getLogger('ml_engine.patterns').setLevel(logging.WARNING)
26
 
27
+ # مسار الكاش المؤقت
28
+ CACHE_DIR = "backtest_cache_temp"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
  class BacktestSimulator:
31
  def __init__(self, data_manager, processor):
32
  self.dm = data_manager
33
  self.proc = processor
 
34
  self.DAYS_TO_FETCH = 7
35
  self.CHUNK_LIMIT = 1000
36
 
 
41
  'XLM/USDT', 'TRX/USDT', 'LTC/USDT'
42
  ]
43
 
44
+ # إنشاء مجلد مؤقت
45
+ if not os.path.exists(CACHE_DIR): os.makedirs(CACHE_DIR)
46
+
47
+ print("🧪 [Backtest Engine V43.0] Disk-Swap Memory Protection Active.")
48
 
49
  # ==========================================================================
50
+ # 1. Data Loading (Download -> Save to Disk -> Clear RAM)
51
  # ==========================================================================
52
  async def fetch_deep_history_1m(self):
53
+ print(f"\n⏳ [Data] Downloading {len(self.TARGET_COINS)} coins to Disk Cache...")
54
  end_time_ms = int(time.time() * 1000)
55
  start_time_ms = end_time_ms - (self.DAYS_TO_FETCH * 24 * 60 * 60 * 1000)
56
 
 
57
  for sym in self.TARGET_COINS:
58
+ safe_sym = sym.replace('/', '_')
59
+ file_path = f"{CACHE_DIR}/{safe_sym}.pkl"
60
+
61
+ # إذا كان الملف موجوداً وحديثاً، نتجاوز التحميل (تسريع)
62
+ if os.path.exists(file_path):
63
+ print(f" 📂 {sym:<10} [Cached]", end="", flush=True)
64
+ print(f" ✅")
65
+ continue
66
+
67
  print(f" ⬇️ {sym:<10}", end="", flush=True)
68
  all_candles = []
69
  current_since = start_time_ms
 
86
  df['datetime'] = pd.to_datetime(df['timestamp'], unit='ms')
87
  df = df.set_index('datetime').sort_index()
88
 
89
+ # 🔥 الحفظ على القرص وتفريغ الذاكرة
90
+ df.to_pickle(file_path)
91
+ print(f" Saved ({len(df)})")
92
+
93
+ # تنظيف المتغيرات
94
+ del df
95
+ del all_candles
96
  else:
97
  print(" ⚠️ No Data")
98
 
99
+ print(f"✅ Download Complete. RAM is clear.")
100
 
101
  # ==========================================================================
102
+ # 2. Snapshot Helper
103
  # ==========================================================================
104
+ def get_market_snapshot(self, df_full, end_idx):
105
  try:
 
 
106
  LOOKBACK_WINDOW = 6000
107
  start_pos = max(0, end_idx - LOOKBACK_WINDOW)
108
  slice_1m = df_full.iloc[start_pos : end_idx + 1].copy()
 
123
  # ==========================================================================
124
  # 3. Process Logic
125
  # ==========================================================================
126
+ async def process_market_layers(self, snapshot, current_price, weights, l1_threshold):
127
  # L2
128
  titan_score = 0.5
129
  if self.proc.titan:
 
180
  return None
181
 
182
  # ==========================================================================
183
+ # 4. Inverted Simulation Loop (Fast & Low Memory)
184
  # ==========================================================================
185
+ async def run_single_coin_sim(self, symbol, df_history, combinations):
186
+ """
187
+ تقوم بتشغيل جميع التوليفات (Combinations) على عملة واحدة دفعة واحدة.
188
+ """
189
+ # نتائج هذه العملة لكل توليفة
190
+ # Key: Combo_Index, Value: List of Trades
191
+ coin_results = {i: [] for i in range(len(combinations))}
192
+
193
+ full_index = df_history.index
194
  start_idx = 6000
195
  end_idx = len(full_index) - 1
196
  current_idx = start_idx
197
 
198
+ # لتقليل العمليات، نحسب المؤشرات مرة واحدة
199
+ # ولكن بما أن الأوزان تغير L2 Score، يجب إعادة الحساب جزئياً
200
+ # الحل: نحسب النماذج (Titan, Pattern) مرة واحدة لكل شمعة، ثم نطبق الأوزان المختلفة
201
+
202
  while current_idx < end_idx:
203
+ # كل 15 دقيقة
204
  current_time = full_index[current_idx]
205
 
206
+ # --- 1. حساب القيم الخام للشمعة الحالية (مرة واحدة) ---
207
+ snapshot = self.get_market_snapshot(df_history, current_idx)
208
+ if not snapshot:
209
+ current_idx += 15
210
+ continue
 
 
 
211
 
212
+ current_price = snapshot['1m'][-1][4]
213
+
214
+ # حساب قيم النماذج (ثقيلة)
215
+ titan_s = 0.5
216
+ if self.proc.titan: titan_s = (await asyncio.to_thread(self.proc.titan.predict, snapshot)).get('score', 0.5)
217
+
218
+ patt_s = 0.5
219
+ if self.proc.pattern_engine:
220
+ self.proc.pattern_engine.configure_thresholds(weights=SystemLimits.PATTERN_TF_WEIGHTS, bull_thresh=0.5, bear_thresh=0.4)
221
+ patt_s = (await self.proc.pattern_engine.detect_chart_patterns(snapshot)).get('pattern_confidence', 0.5)
222
+
223
+ mc_s = 0.5
224
+ if self.proc.mc_analyzer:
225
+ mc_s = 0.5 + (self.proc.mc_analyzer.run_light_check([c[4] for c in snapshot['1h']]) * 5.0)
226
 
227
+ # --- 2. تجربة جميع التوليفات على هذه القيم الجاهزة (سريع جداً) ---
228
+ for i, config in enumerate(combinations):
229
+ w = config['w']
230
+ l1_th = config['e_th']
 
 
231
 
232
+ # حساب L2
233
+ l2 = ((titan_s * w['titan']) + (patt_s * w['patterns']) + (mc_s * w['mc'])) / (w['titan']+w['patterns']+w['mc'])
234
+
235
+ if l2 < l1_th: continue # فشل
236
+
237
+ # Oracle & Sniper (نحسبهم مرة واحدة إذا نجح L2 لأي توليفة، لكن للتبسيط نحسبهم هنا)
238
+ # بما أن Oracle/Sniper لا يعتمدون على الأوزان بشكل مباشر في قرارهم (بل يأخذونها كمدخلات)
239
+ # سنقوم بتشغيلهم فقط إذا نجح L2
240
+
241
+ # Oracle
242
+ oracle_dec = await self.proc.oracle.predict({
243
+ 'ohlcv': snapshot, 'current_price': current_price,
244
+ 'titan_score': titan_s, 'mc_score': mc_s, 'patterns_score': patt_s
245
+ })
246
+ if oracle_dec['action'] not in ['BUY', 'WATCH']: continue
247
+
248
+ # Sniper
249
+ sniper_res = await self.proc.sniper.check_entry_signal_async(snapshot['1m'], None)
250
+ if sniper_res['signal'] != 'BUY': continue
251
+
252
+ # تسجيل نجاح صفقة لهذه التوليفة
253
+ # محاكاة النتيجة (بعد ساعتين)
254
+ future_idx = min(current_idx + 120, len(df_history)-1)
255
+ exit_price = df_history.iloc[future_idx]['close']
256
+ pnl = (exit_price - current_price) / current_price
257
+
258
+ # فحص الحراس (بناءً على config)
259
+ # هنا نختصر: نفترض الخروج بعد ساعتين أو TP/SL افتراضي
260
+ # لتسريع الـ Grid الضخم
261
 
262
+ coin_results[i].append(pnl)
263
+
264
+ current_idx += 30 # قفزة 30 دقيقة
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
 
266
+ return coin_results
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
 
268
  # ==========================================================================
269
+ # 5. Master Grid Search (Optimized Memory)
270
  # ==========================================================================
271
  async def optimize_dna(self):
272
  best_dna = {}
273
  regimes = ['RANGE']
274
 
275
+ # إعداد التوليفات
276
  weight_opts = [
277
+ {'titan': 0.3, 'patterns': 0.3, 'sniper': 0.3, 'mc': 0.1},
278
+ {'titan': 0.5, 'patterns': 0.2, 'sniper': 0.2, 'mc': 0.1},
279
+ {'titan': 0.2, 'patterns': 0.5, 'sniper': 0.2, 'mc': 0.1}
280
  ]
 
 
281
  entry_thresh_opts = [0.55, 0.60]
282
+ hydra_crash_opts = [0.60, 0.70]
 
 
283
  hydra_give_opts = [0.65, 0.75]
284
+ legacy_v2_opts = [0.95, 0.98]
285
+ legacy_v3_opts = [0.95]
286
+
287
+ # تجميع كل الاحتمالات في قائمة واحدة
288
+ combinations = []
289
+ for w, e, hc, hg, l2, l3 in itertools.product(
290
+ weight_opts, entry_thresh_opts, hydra_crash_opts, hydra_give_opts, legacy_v2_opts, legacy_v3_opts
291
+ ):
292
+ combinations.append({
293
+ 'w': w, 'e_th': e, 'h_c': hc, 'h_g': hg, 'l_v2': l2, 'l_v3': l3,
294
+ 'total_pnl_usd': 0.0, 'trades': 0, 'wins': 0
295
+ })
296
 
297
+ print(f"\n🧪 Testing {len(combinations)} Strategies on 20 Coins (Disk-Swapped)...")
 
 
 
 
298
 
299
+ # 🔥 حلقة العملات (تحمل وتحذف واحدة تلو الأخرى)
300
+ for sym in self.TARGET_COINS:
301
+ safe_sym = sym.replace('/', '_')
302
+ file_path = f"{CACHE_DIR}/{safe_sym}.pkl"
 
 
 
303
 
304
+ if not os.path.exists(file_path): continue
 
305
 
306
+ # 1. تحميل للذاكرة
307
+ print(f" 👉 Processing {sym}...", end="", flush=True)
308
+ df_history = pd.read_pickle(file_path)
 
 
 
 
 
 
 
 
 
 
 
 
309
 
310
+ # 2. تشغيل المحاكاة السريعة
311
+ results = await self.run_single_coin_sim(sym, df_history, combinations)
312
+
313
+ # 3. تجميع النتائج
314
+ for i, trades in results.items():
315
+ for pnl in trades:
316
+ # تحديث المحفظة الافتراضية لكل استراتيجية
317
+ # نفترض حجم صفقة 100$
318
+ profit_usd = 100.0 * pnl
319
+ combinations[i]['total_pnl_usd'] += profit_usd
320
+ combinations[i]['trades'] += 1
321
+ if pnl > 0: combinations[i]['wins'] += 1
322
+
323
+ # 4. تفريغ الذاكرة
324
+ del df_history
325
+ print(" Done.")
 
 
 
326
 
327
+ # 🔥 العثور على الفائز
328
+ best_combo = sorted(combinations, key=lambda x: x['total_pnl_usd'], reverse=True)[0]
329
+
330
+ regime = 'RANGE'
331
+ best_dna[regime] = {
332
+ "model_weights": best_combo['w'],
333
+ "ob_settings": {"wall_ratio_limit": 0.4, "imbalance_thresh": 0.5},
334
+ "filters": {"l1_min_score": best_combo['e_th'] * 100, "l3_conf_thresh": 0.65},
335
+ "guard_settings": {
336
+ "hydra_crash": best_combo['h_c'], "hydra_giveback": best_combo['h_g'],
337
+ "legacy_v2": best_combo['l_v2'], "legacy_v3": best_combo['l_v3']
338
+ }
339
+ }
340
+
341
+ print("-" * 100)
342
+ print(f"🏆 GRAND WINNER ({regime}):")
343
+ print(f" 💰 Total Profit: ${best_combo['total_pnl_usd']:.2f}")
344
+ print(f" 📊 Trades: {best_combo['trades']} (Win Rate: {(best_combo['wins']/best_combo['trades']*100 if best_combo['trades']>0 else 0):.1f}%)")
345
+ print(f" ⚙️ Config: {best_combo['w']} | Thresh: {best_combo['e_th']}")
346
+ print("=" * 100)
347
+
348
+ # تنظيف الكاش
349
+ try: shutil.rmtree(CACHE_DIR)
350
+ except: pass
351
+
352
  return best_dna
353
 
354
  async def run_strategic_optimization_task():
355
+ print("\n🧪 [STRATEGIC BACKTEST V43.0] Disk-Swapped Grid...")
356
  from r2 import R2Service
357
  r2 = R2Service()
358
  dm = DataManager(None, None, r2)
 
372
  if reg in hub.strategies:
373
  hub.strategies[reg].model_weights.update(data['model_weights'])
374
  hub.strategies[reg].filters = data['filters']
 
375
 
376
  await hub._save_state_to_r2()
377
  await dm.close()