Riy777 commited on
Commit
e3af292
·
verified ·
1 Parent(s): d420fd1

Update backtest_engine.py

Browse files
Files changed (1) hide show
  1. backtest_engine.py +92 -67
backtest_engine.py CHANGED
@@ -1,5 +1,5 @@
1
  # ============================================================
2
- # 🧪 backtest_engine.py (V69.0 - GEM-Architect: Detailed Analytics)
3
  # ============================================================
4
 
5
  import asyncio
@@ -27,48 +27,42 @@ class HeavyDutyBacktester:
27
  def __init__(self, data_manager, processor):
28
  self.dm = data_manager
29
  self.proc = processor
30
- self.GRID_DENSITY = 15
31
- self.BACKTEST_DAYS = 7 # 7 أيام كافية للتحسين السريع، 14 للدقة العالية
32
 
33
  # 💰 إعدادات التوأم الرقمي
34
  self.INITIAL_CAPITAL = 10.0
35
  self.TRADING_FEES = 0.001
36
  self.MAX_SLOTS = 4
37
 
38
- self.TARGET_COINS = [ 'SOL/USDT', 'XRP/USDT', 'DOGE/USDT', 'ADA/USDT', 'AVAX/USDT', 'LINK/USDT', 'TON/USDT', 'INJ/USDT', 'APT/USDT', 'OP/USDT', 'ARB/USDT', 'SUI/USDT', 'SEI/USDT', 'TIA/USDT', 'MATIC/USDT', 'PEPE/USDT', 'NEAR/USDT', 'RUNE/USDT', 'PYTH/USDT', 'WIF/USDT' ]
 
 
 
 
 
39
 
40
  if not os.path.exists(CACHE_DIR): os.makedirs(CACHE_DIR)
41
- print(f"🧪 [Backtest V69.0] Detailed Analytics Mode Active.")
42
 
43
  # ==============================================================
44
  # 🛠️ Helpers
45
  # ==============================================================
46
- def resample_data(self, df_1m, timeframe_str):
47
- if df_1m.empty: return pd.DataFrame()
48
- agg_dict = {'open': 'first', 'high': 'max', 'low': 'min', 'close': 'last', 'volume': 'sum'}
49
- rule = timeframe_str.replace('m', 'T').replace('h', 'H').replace('d', 'D')
50
- try:
51
- resampled = df_1m.resample(rule).agg(agg_dict).dropna()
52
- resampled['timestamp'] = resampled.index.astype(np.int64) // 10**6
53
- return resampled
54
- except Exception: return pd.DataFrame()
55
-
56
  def df_to_list(self, df):
57
  if df.empty: return []
58
  return df[['timestamp', 'open', 'high', 'low', 'close', 'volume']].values.tolist()
59
 
60
  # ==============================================================
61
- # PHASE 1: Generate Truth Data (Strict Logic Tree)
62
  # ==============================================================
63
  async def generate_truth_data(self):
64
- # (نفس منطق V68 السابق - لم يتغير لأنه صحيح)
65
- print(f"\n🚜 [Phase 1] Replicating V45.0 Logic Tree ({self.BACKTEST_DAYS} Days)...")
66
  end_time_ms = int(time.time() * 1000)
67
  start_time_ms = end_time_ms - (self.BACKTEST_DAYS * 24 * 60 * 60 * 1000)
68
 
69
  for sym in self.TARGET_COINS:
70
  safe_sym = sym.replace('/', '_')
71
- scores_file = f"{CACHE_DIR}/{safe_sym}_logictree_scores.pkl"
72
 
73
  if os.path.exists(scores_file):
74
  print(f" 📂 {sym} scores ready. Skipping.")
@@ -76,6 +70,7 @@ class HeavyDutyBacktester:
76
 
77
  print(f" ⚙️ Simulating {sym}...", end="", flush=True)
78
 
 
79
  all_candles_1m = []
80
  current_since = start_time_ms
81
  while current_since < end_time_ms:
@@ -86,9 +81,9 @@ class HeavyDutyBacktester:
86
  if last_ts <= current_since: break
87
  all_candles_1m.extend(batch)
88
  current_since = last_ts + 1
89
- await asyncio.sleep(0.05)
90
  if current_since >= end_time_ms: break
91
- except: await asyncio.sleep(1)
92
 
93
  all_candles_1m = [c for c in all_candles_1m if c[0] <= end_time_ms]
94
  if not all_candles_1m:
@@ -100,47 +95,87 @@ class HeavyDutyBacktester:
100
  df_1m.set_index('datetime', inplace=True)
101
  df_1m = df_1m.sort_index()
102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  ai_results = []
104
- resample_freq = '15T'
105
- time_indices = df_1m.resample(resample_freq).last().dropna().index
106
 
107
- for t_idx in time_indices[200:]:
108
- current_slice_1m = df_1m.loc[:t_idx]
109
- if len(current_slice_1m) < 500: continue
110
- current_price = current_slice_1m['close'].iloc[-1]
 
 
 
 
111
 
112
- df_1h = self.resample_data(current_slice_1m.tail(6000), '1h')
113
- df_15m = self.resample_data(current_slice_1m.tail(1500), '15m')
 
 
 
 
 
 
 
 
 
 
114
 
115
- if len(df_1h) < 60 or len(df_15m) < 60: continue
116
 
117
- simulated_data_packet = {
 
118
  'symbol': sym,
119
- 'ohlcv_1h': self.df_to_list(df_1h.tail(60)),
120
- 'ohlcv_15m': self.df_to_list(df_15m.tail(60)),
121
  'change_24h': 0.0
122
  }
123
 
124
  try:
125
- price_24h_ago = df_1h.iloc[-24]['close'] if len(df_1h) >= 24 else df_1h.iloc[0]['close']
126
- simulated_data_packet['change_24h'] = ((current_price - price_24h_ago) / price_24h_ago) * 100
 
 
127
  except: pass
128
 
129
- # استدعاء المنطق الصارم
130
- logic_result = self.dm._apply_logic_tree(simulated_data_packet)
131
-
132
  signal_type = logic_result.get('type', 'NONE')
133
  l1_score = logic_result.get('score', 0.0)
134
 
135
- # Titan Simulation Placeholder
136
- titan_real = 0.5
137
-
138
  if signal_type in ['BREAKOUT', 'REVERSAL']:
 
 
 
 
 
 
 
 
 
 
 
139
  ai_results.append({
140
- 'timestamp': int(t_idx.timestamp() * 1000),
141
  'symbol': sym,
142
  'close': current_price,
143
- 'real_titan': titan_real,
144
  'signal_type': signal_type,
145
  'l1_score': l1_score
146
  })
@@ -149,10 +184,10 @@ class HeavyDutyBacktester:
149
  pd.DataFrame(ai_results).to_pickle(scores_file)
150
  print(f" ✅ Saved ({len(ai_results)} signals).")
151
  else:
152
- print(" ⚠️ No strict signals found.")
153
 
154
  # ==============================================================
155
- # PHASE 2: Portfolio Digital Twin Engine (Enhanced Stats)
156
  # ==============================================================
157
  @staticmethod
158
  def _worker_optimize(combinations_batch, scores_files, initial_capital, fees_pct, max_slots):
@@ -176,7 +211,7 @@ class HeavyDutyBacktester:
176
  "balance": initial_capital,
177
  "allocated": 0.0,
178
  "positions": {},
179
- "trades_history": [] # Store {pnl: float}
180
  }
181
 
182
  w_titan = config['w_titan']
@@ -237,12 +272,11 @@ class HeavyDutyBacktester:
237
 
238
  if wallet["balance"] < 1.0 and len(wallet["positions"]) == 0: break
239
 
240
- # 🔥🔥🔥 إحصائيات تفصيلية (Detailed Analytics) 🔥🔥🔥
241
  trades = wallet["trades_history"]
242
  if trades:
243
  net_profit = wallet["balance"] - initial_capital
244
 
245
- # 1. الربح والخسارة
246
  pnls = [t['pnl'] for t in trades]
247
  wins = [p for p in pnls if p > 0]
248
  losses = [p for p in pnls if p <= 0]
@@ -252,15 +286,11 @@ class HeavyDutyBacktester:
252
  total_trades = len(trades)
253
  win_rate = (win_count / total_trades) * 100
254
 
255
- # 2. أقصى ربح وخسارة فردية
256
  max_single_win = max(pnls) if pnls else 0.0
257
  max_single_loss = min(pnls) if pnls else 0.0
258
 
259
- # 3. السلاسل المتتالية (Streaks)
260
- current_win_streak = 0
261
- max_win_streak = 0
262
- current_loss_streak = 0
263
- max_loss_streak = 0
264
 
265
  for p in pnls:
266
  if p > 0:
@@ -287,13 +317,9 @@ class HeavyDutyBacktester:
287
  })
288
  else:
289
  results.append({
290
- 'config': config,
291
- 'final_balance': initial_capital,
292
- 'net_profit': 0.0,
293
- 'total_trades': 0,
294
- 'win_count': 0, 'loss_count': 0, 'win_rate': 0.0,
295
- 'max_single_win': 0.0, 'max_single_loss': 0.0,
296
- 'max_win_streak': 0, 'max_loss_streak': 0
297
  })
298
 
299
  return results
@@ -301,12 +327,12 @@ class HeavyDutyBacktester:
301
  async def run_optimization(self):
302
  await self.generate_truth_data()
303
 
304
- score_files = [os.path.join(CACHE_DIR, f) for f in os.listdir(CACHE_DIR) if f.endswith(f'_logictree_scores.pkl')]
305
  if not score_files:
306
- print("❌ No Strict Logic signals found.")
307
  return None
308
 
309
- print(f"\n🧩 [Phase 2] Running Strict Logic Simulation...")
310
  print(f" 💰 Start Capital: ${self.INITIAL_CAPITAL}")
311
 
312
  w_titan_range = np.linspace(0.4, 0.9, num=self.GRID_DENSITY)
@@ -338,9 +364,8 @@ class HeavyDutyBacktester:
338
 
339
  best = sorted(final_results, key=lambda x: x['final_balance'], reverse=True)[0]
340
 
341
- # 🔥🔥🔥 التقرير التفصيلي الكامل 🔥🔥🔥
342
  print("\n" + "="*60)
343
- print(f"🏆 CHAMPION STRICT REPORT ({self.BACKTEST_DAYS} Days):")
344
  print(f" 💰 Final Balance: ${best['final_balance']:,.2f}")
345
  print(f" 🚀 Net PnL: ${best['net_profit']:,.2f}")
346
  print("-" * 60)
@@ -360,7 +385,7 @@ class HeavyDutyBacktester:
360
  return best['config']
361
 
362
  async def run_strategic_optimization_task():
363
- print("\n🧪 [STRATEGIC BACKTEST] Starting Logic Tree Optimization...")
364
  r2 = R2Service()
365
  dm = DataManager(None, None, r2)
366
  proc = MLProcessor(dm)
 
1
  # ============================================================
2
+ # 🧪 backtest_engine.py (V71.1 - GEM-Architect: 6-Frame Turbo)
3
  # ============================================================
4
 
5
  import asyncio
 
27
  def __init__(self, data_manager, processor):
28
  self.dm = data_manager
29
  self.proc = processor
30
+ self.GRID_DENSITY = 10
31
+ self.BACKTEST_DAYS = 7
32
 
33
  # 💰 إعدادات التوأم الرقمي
34
  self.INITIAL_CAPITAL = 10.0
35
  self.TRADING_FEES = 0.001
36
  self.MAX_SLOTS = 4
37
 
38
+ self.TARGET_COINS = [
39
+ 'SOL/USDT', 'XRP/USDT', 'DOGE/USDT', 'ADA/USDT', 'AVAX/USDT',
40
+ 'LINK/USDT', 'TON/USDT', 'INJ/USDT', 'APT/USDT', 'OP/USDT',
41
+ 'ARB/USDT', 'SUI/USDT', 'SEI/USDT', 'TIA/USDT', 'MATIC/USDT',
42
+ 'PEPE/USDT', 'NEAR/USDT', 'RUNE/USDT', 'PYTH/USDT', 'WIF/USDT'
43
+ ]
44
 
45
  if not os.path.exists(CACHE_DIR): os.makedirs(CACHE_DIR)
46
+ print(f"🧪 [Backtest V71.1] 6-Frame Turbo Mode (1m Included).")
47
 
48
  # ==============================================================
49
  # 🛠️ Helpers
50
  # ==============================================================
 
 
 
 
 
 
 
 
 
 
51
  def df_to_list(self, df):
52
  if df.empty: return []
53
  return df[['timestamp', 'open', 'high', 'low', 'close', 'volume']].values.tolist()
54
 
55
  # ==============================================================
56
+ # PHASE 1: Generate Truth Data (Multi-Frame Vectorization)
57
  # ==============================================================
58
  async def generate_truth_data(self):
59
+ print(f"\n🚜 [Phase 1] Processing Logic Tree + Titan ({self.BACKTEST_DAYS} Days)...")
 
60
  end_time_ms = int(time.time() * 1000)
61
  start_time_ms = end_time_ms - (self.BACKTEST_DAYS * 24 * 60 * 60 * 1000)
62
 
63
  for sym in self.TARGET_COINS:
64
  safe_sym = sym.replace('/', '_')
65
+ scores_file = f"{CACHE_DIR}/{safe_sym}_fullstack_scores.pkl"
66
 
67
  if os.path.exists(scores_file):
68
  print(f" 📂 {sym} scores ready. Skipping.")
 
70
 
71
  print(f" ⚙️ Simulating {sym}...", end="", flush=True)
72
 
73
+ # 1. جلب بيانات الدقيقة الخام
74
  all_candles_1m = []
75
  current_since = start_time_ms
76
  while current_since < end_time_ms:
 
81
  if last_ts <= current_since: break
82
  all_candles_1m.extend(batch)
83
  current_since = last_ts + 1
84
+ await asyncio.sleep(0.01)
85
  if current_since >= end_time_ms: break
86
+ except: await asyncio.sleep(0.5)
87
 
88
  all_candles_1m = [c for c in all_candles_1m if c[0] <= end_time_ms]
89
  if not all_candles_1m:
 
95
  df_1m.set_index('datetime', inplace=True)
96
  df_1m = df_1m.sort_index()
97
 
98
+ # 🔥🔥🔥 التحضير المسبق (Vectorization) 🔥🔥🔥
99
+ agg_dict = {'open': 'first', 'high': 'max', 'low': 'min', 'close': 'last', 'volume': 'sum'}
100
+
101
+ frames = {}
102
+
103
+ # ✅ إضافة إطار الدقيقة (1m) مباشرة
104
+ # لا نحتاج Resample لأنه هو الأساس، فقط نضيف عمود timestamp
105
+ df_1m_ready = df_1m.copy()
106
+ df_1m_ready['timestamp'] = df_1m_ready.index.astype(np.int64) // 10**6
107
+ frames['1m'] = df_1m_ready
108
+
109
+ # ✅ إضافة باقي الأطر (Resampling)
110
+ for tf_str, tf_code in [('5m', '5T'), ('15m', '15T'), ('1h', '1h'), ('4h', '4h'), ('1d', '1D')]:
111
+ resampled = df_1m.resample(tf_code).agg(agg_dict).dropna()
112
+ resampled['timestamp'] = resampled.index.astype(np.int64) // 10**6
113
+ frames[tf_str] = resampled
114
+
115
  ai_results = []
 
 
116
 
117
+ # نتحرك بناءً على إطار الـ 5 دقائق كقاعدة زمنية
118
+ valid_indices = frames['5m'].index[500:]
119
+
120
+ for t_idx in valid_indices:
121
+ current_timestamp = int(t_idx.timestamp() * 1000)
122
+
123
+ # 🔥 قص البيانات لجميع الأطر الستة (1m included)
124
+ ohlcv_data = {}
125
 
126
+ try:
127
+ # [FIXED] إضافة 1m للحزمة (نأخذ 500 شمعة للدقة العالية)
128
+ ohlcv_data['1m'] = self.df_to_list(frames['1m'].loc[:t_idx].tail(500))
129
+
130
+ ohlcv_data['5m'] = self.df_to_list(frames['5m'].loc[:t_idx].tail(200))
131
+ ohlcv_data['15m'] = self.df_to_list(frames['15m'].loc[:t_idx].tail(200))
132
+ ohlcv_data['1h'] = self.df_to_list(frames['1h'].loc[:t_idx].tail(200))
133
+ ohlcv_data['4h'] = self.df_to_list(frames['4h'].loc[:t_idx].tail(100))
134
+ ohlcv_data['1d'] = self.df_to_list(frames['1d'].loc[:t_idx].tail(50))
135
+ except: continue
136
+
137
+ if len(ohlcv_data['1h']) < 60: continue
138
 
139
+ current_price = frames['5m'].loc[t_idx]['close']
140
 
141
+ # 1. استدعاء شجرة المنطق (Logic Tree)
142
+ logic_packet = {
143
  'symbol': sym,
144
+ 'ohlcv_1h': ohlcv_data['1h'][-60:],
145
+ 'ohlcv_15m': ohlcv_data['15m'][-60:],
146
  'change_24h': 0.0
147
  }
148
 
149
  try:
150
+ if len(ohlcv_data['1h']) >= 24:
151
+ p_now = ohlcv_data['1h'][-1][4]
152
+ p_old = ohlcv_data['1h'][-24][4]
153
+ logic_packet['change_24h'] = ((p_now - p_old) / p_old) * 100
154
  except: pass
155
 
156
+ logic_result = self.dm._apply_logic_tree(logic_packet)
 
 
157
  signal_type = logic_result.get('type', 'NONE')
158
  l1_score = logic_result.get('score', 0.0)
159
 
160
+ # 2. استدعاء Titan (Real AI)
161
+ real_titan = 0.5
 
162
  if signal_type in ['BREAKOUT', 'REVERSAL']:
163
+ raw_data_for_proc = {
164
+ 'symbol': sym,
165
+ 'ohlcv': ohlcv_data, # ✅ الآن تحتوي على 1m
166
+ 'current_price': current_price
167
+ }
168
+ try:
169
+ proc_res = await self.proc.process_compound_signal(raw_data_for_proc)
170
+ if proc_res:
171
+ real_titan = proc_res.get('titan_score', 0.5)
172
+ except: pass
173
+
174
  ai_results.append({
175
+ 'timestamp': current_timestamp,
176
  'symbol': sym,
177
  'close': current_price,
178
+ 'real_titan': real_titan,
179
  'signal_type': signal_type,
180
  'l1_score': l1_score
181
  })
 
184
  pd.DataFrame(ai_results).to_pickle(scores_file)
185
  print(f" ✅ Saved ({len(ai_results)} signals).")
186
  else:
187
+ print(" ⚠️ No signals.")
188
 
189
  # ==============================================================
190
+ # PHASE 2: Portfolio Digital Twin Engine
191
  # ==============================================================
192
  @staticmethod
193
  def _worker_optimize(combinations_batch, scores_files, initial_capital, fees_pct, max_slots):
 
211
  "balance": initial_capital,
212
  "allocated": 0.0,
213
  "positions": {},
214
+ "trades_history": []
215
  }
216
 
217
  w_titan = config['w_titan']
 
272
 
273
  if wallet["balance"] < 1.0 and len(wallet["positions"]) == 0: break
274
 
275
+ # Detailed Analytics
276
  trades = wallet["trades_history"]
277
  if trades:
278
  net_profit = wallet["balance"] - initial_capital
279
 
 
280
  pnls = [t['pnl'] for t in trades]
281
  wins = [p for p in pnls if p > 0]
282
  losses = [p for p in pnls if p <= 0]
 
286
  total_trades = len(trades)
287
  win_rate = (win_count / total_trades) * 100
288
 
 
289
  max_single_win = max(pnls) if pnls else 0.0
290
  max_single_loss = min(pnls) if pnls else 0.0
291
 
292
+ current_win_streak = 0; max_win_streak = 0
293
+ current_loss_streak = 0; max_loss_streak = 0
 
 
 
294
 
295
  for p in pnls:
296
  if p > 0:
 
317
  })
318
  else:
319
  results.append({
320
+ 'config': config, 'final_balance': initial_capital, 'net_profit': 0.0,
321
+ 'total_trades': 0, 'win_count': 0, 'loss_count': 0, 'win_rate': 0.0,
322
+ 'max_single_win': 0.0, 'max_single_loss': 0.0, 'max_win_streak': 0, 'max_loss_streak': 0
 
 
 
 
323
  })
324
 
325
  return results
 
327
  async def run_optimization(self):
328
  await self.generate_truth_data()
329
 
330
+ score_files = [os.path.join(CACHE_DIR, f) for f in os.listdir(CACHE_DIR) if f.endswith(f'_fullstack_scores.pkl')]
331
  if not score_files:
332
+ print("❌ No Full-Stack signals found.")
333
  return None
334
 
335
+ print(f"\n🧩 [Phase 2] Running Full-Stack Simulation...")
336
  print(f" 💰 Start Capital: ${self.INITIAL_CAPITAL}")
337
 
338
  w_titan_range = np.linspace(0.4, 0.9, num=self.GRID_DENSITY)
 
364
 
365
  best = sorted(final_results, key=lambda x: x['final_balance'], reverse=True)[0]
366
 
 
367
  print("\n" + "="*60)
368
+ print(f"🏆 CHAMPION FULL-STACK REPORT ({self.BACKTEST_DAYS} Days):")
369
  print(f" 💰 Final Balance: ${best['final_balance']:,.2f}")
370
  print(f" 🚀 Net PnL: ${best['net_profit']:,.2f}")
371
  print("-" * 60)
 
385
  return best['config']
386
 
387
  async def run_strategic_optimization_task():
388
+ print("\n🧪 [STRATEGIC BACKTEST] Starting Full-Stack Optimization...")
389
  r2 = R2Service()
390
  dm = DataManager(None, None, r2)
391
  proc = MLProcessor(dm)