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

Update backtest_engine.py

Browse files
Files changed (1) hide show
  1. backtest_engine.py +85 -54
backtest_engine.py CHANGED
@@ -1,5 +1,5 @@
1
  # ============================================================
2
- # 🧪 backtest_engine.py (V68.0 - GEM-Architect: Logic Tree Simulator)
3
  # ============================================================
4
 
5
  import asyncio
@@ -27,24 +27,21 @@ class HeavyDutyBacktester:
27
  def __init__(self, data_manager, processor):
28
  self.dm = data_manager
29
  self.proc = processor
30
- self.GRID_DENSITY = 5
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
- 'BTC/USDT', 'ETH/USDT', 'SOL/USDT', 'BNB/USDT', 'XRP/USDT',
40
- 'DOGE/USDT', 'ADA/USDT', 'AVAX/USDT'
41
- ]
42
 
43
  if not os.path.exists(CACHE_DIR): os.makedirs(CACHE_DIR)
44
- print(f"🧪 [Backtest V68.0] Logic Tree Simulator (Anti-FOMO).")
45
 
46
  # ==============================================================
47
- # 🛠️ Helpers (Resampling Exact Match)
48
  # ==============================================================
49
  def resample_data(self, df_1m, timeframe_str):
50
  if df_1m.empty: return pd.DataFrame()
@@ -52,20 +49,19 @@ class HeavyDutyBacktester:
52
  rule = timeframe_str.replace('m', 'T').replace('h', 'H').replace('d', 'D')
53
  try:
54
  resampled = df_1m.resample(rule).agg(agg_dict).dropna()
55
- # التأكد من إعادة التسمية الصحيحة للأعمدة
56
  resampled['timestamp'] = resampled.index.astype(np.int64) // 10**6
57
  return resampled
58
  except Exception: return pd.DataFrame()
59
 
60
  def df_to_list(self, df):
61
  if df.empty: return []
62
- # ترتيب الأعمدة كما يتوقعها DataManager القديم والجديد
63
  return df[['timestamp', 'open', 'high', 'low', 'close', 'volume']].values.tolist()
64
 
65
  # ==============================================================
66
  # PHASE 1: Generate Truth Data (Strict Logic Tree)
67
  # ==============================================================
68
  async def generate_truth_data(self):
 
69
  print(f"\n🚜 [Phase 1] Replicating V45.0 Logic Tree ({self.BACKTEST_DAYS} Days)...")
70
  end_time_ms = int(time.time() * 1000)
71
  start_time_ms = end_time_ms - (self.BACKTEST_DAYS * 24 * 60 * 60 * 1000)
@@ -80,7 +76,6 @@ class HeavyDutyBacktester:
80
 
81
  print(f" ⚙️ Simulating {sym}...", end="", flush=True)
82
 
83
- # 1. جلب بيانات الدقيقة (الخام)
84
  all_candles_1m = []
85
  current_since = start_time_ms
86
  while current_since < end_time_ms:
@@ -106,70 +101,58 @@ class HeavyDutyBacktester:
106
  df_1m = df_1m.sort_index()
107
 
108
  ai_results = []
109
- # نفحص كل 15 دقيقة (لأن الفلتر يعمل على إطار الربع ساعة والساعة)
110
  resample_freq = '15T'
111
  time_indices = df_1m.resample(resample_freq).last().dropna().index
112
 
113
- for t_idx in time_indices[200:]: # نبدأ بعد فترة لضمان وجود بيانات كافية للمؤشرات
114
  current_slice_1m = df_1m.loc[:t_idx]
115
  if len(current_slice_1m) < 500: continue
116
  current_price = current_slice_1m['close'].iloc[-1]
117
 
118
- # 🔥 المحاكاة الدقيقة: بناء المدخلات كما يطلبها _apply_logic_tree
119
- # نحتاج شموع 1 ساعة و 15 دقيقة
120
- # نأخذ بيانات كافية (آخر 100 شمعة لكل إطار)
121
-
122
  df_1h = self.resample_data(current_slice_1m.tail(6000), '1h')
123
  df_15m = self.resample_data(current_slice_1m.tail(1500), '15m')
124
 
125
  if len(df_1h) < 60 or len(df_15m) < 60: continue
126
 
127
- # تحويل البيانات إلى القوائم التي يتوقعها DataManager V45
128
  simulated_data_packet = {
129
  'symbol': sym,
130
  'ohlcv_1h': self.df_to_list(df_1h.tail(60)),
131
  'ohlcv_15m': self.df_to_list(df_15m.tail(60)),
132
- 'change_24h': 0.0 # (يمكن حسابه بدقة أكبر إذا لزم الأمر، لكن الفلتر يعتمد على 4H أكثر)
133
  }
134
 
135
- # حساب نسبة التغير اليومي للمحاكاة (للصرامة)
136
  try:
137
  price_24h_ago = df_1h.iloc[-24]['close'] if len(df_1h) >= 24 else df_1h.iloc[0]['close']
138
  simulated_data_packet['change_24h'] = ((current_price - price_24h_ago) / price_24h_ago) * 100
139
  except: pass
140
 
141
- # 🔥 استدعاء المنطق الصارم مباشرة
142
- # هذا يضمن أن الباكتست يرى بالضبط ما يراه النظام الحي (Breakout/Reversal/None)
143
  logic_result = self.dm._apply_logic_tree(simulated_data_packet)
144
 
145
  signal_type = logic_result.get('type', 'NONE')
146
  l1_score = logic_result.get('score', 0.0)
147
 
148
- # تخزين النتيجة فقط إذا كان هناك إشارة (لتوفير الذاكرة وتسريع المعالجة)
149
- # أو تخزين الكل إذا أردنا تدريب Titan على الرفض
150
-
151
- # محاكاة Titan (اختياري، للسرعة نضع قيمة افتراضية أو نستدعي النموذج)
152
  titan_real = 0.5
153
 
154
- # إذا كانت الإشارة مقبولة، نحفظها
155
  if signal_type in ['BREAKOUT', 'REVERSAL']:
156
  ai_results.append({
157
  'timestamp': int(t_idx.timestamp() * 1000),
158
  'symbol': sym,
159
  'close': current_price,
160
  'real_titan': titan_real,
161
- 'signal_type': signal_type, # ✅ نوع الإشارة (مهم جداً)
162
- 'l1_score': l1_score # ✅ قوة الإشارة
163
  })
164
 
165
  if ai_results:
166
  pd.DataFrame(ai_results).to_pickle(scores_file)
167
  print(f" ✅ Saved ({len(ai_results)} signals).")
168
  else:
169
- print(" ⚠️ No strict signals found (Expected behavior for Anti-FOMO).")
170
 
171
  # ==============================================================
172
- # PHASE 2: Portfolio Digital Twin Engine
173
  # ==============================================================
174
  @staticmethod
175
  def _worker_optimize(combinations_batch, scores_files, initial_capital, fees_pct, max_slots):
@@ -193,11 +176,11 @@ class HeavyDutyBacktester:
193
  "balance": initial_capital,
194
  "allocated": 0.0,
195
  "positions": {},
196
- "trades_history": []
197
  }
198
 
199
  w_titan = config['w_titan']
200
- w_struct = config['w_struct'] # هذا سيكون وزناً لنوع الإشارة وقوتها
201
  entry_thresh = config['thresh']
202
 
203
  for ts, group in grouped_by_time:
@@ -221,7 +204,7 @@ class HeavyDutyBacktester:
221
  del wallet["positions"][sym]
222
  wallet["trades_history"].append({'pnl': net_pnl})
223
 
224
- # 2. Entry Logic (Strict)
225
  if len(wallet["positions"]) < max_slots:
226
  free_capital = wallet["balance"] - wallet["allocated"]
227
  slots_left = max_slots - len(wallet["positions"])
@@ -235,21 +218,13 @@ class HeavyDutyBacktester:
235
  sym = row['symbol']
236
  if sym in wallet["positions"]: continue
237
 
238
- # 🔥 استخدام البيانات الجديدة
239
- sig_type = row['signal_type'] # BREAKOUT or REVERSAL
240
- l1_raw_score = row['l1_score'] # Score from Logic Tree
241
  real_titan = row['real_titan']
242
 
243
- # تطبيع سكور الـ L1 ليكون متوافقاً مع المعادلة (0-1)
244
- # Breakout score عادة يكون صغيراً (1.5 - 5.0 ratio)
245
- # Reversal score (0-100)
246
-
247
  norm_struct = 0.0
248
- if sig_type == 'BREAKOUT':
249
- # كلما زاد الفوليوم كان أفضل، لنقل 5.0 هو الحد الأقصى للطبيعي
250
- norm_struct = min(1.0, l1_raw_score / 5.0)
251
- elif sig_type == 'REVERSAL':
252
- norm_struct = l1_raw_score / 100.0
253
 
254
  score = 0.0
255
  if (w_titan + w_struct) > 0:
@@ -262,18 +237,64 @@ class HeavyDutyBacktester:
262
 
263
  if wallet["balance"] < 1.0 and len(wallet["positions"]) == 0: break
264
 
265
- # Statistics
266
  trades = wallet["trades_history"]
267
  if trades:
268
  net_profit = wallet["balance"] - initial_capital
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  results.append({
270
  'config': config,
271
  'final_balance': wallet["balance"],
272
  'net_profit': net_profit,
273
- 'total_trades': len(trades)
 
 
 
 
 
 
 
274
  })
275
  else:
276
- results.append({'config': config, 'final_balance': initial_capital, 'net_profit': 0.0, 'total_trades': 0})
 
 
 
 
 
 
 
 
277
 
278
  return results
279
 
@@ -282,7 +303,7 @@ class HeavyDutyBacktester:
282
 
283
  score_files = [os.path.join(CACHE_DIR, f) for f in os.listdir(CACHE_DIR) if f.endswith(f'_logictree_scores.pkl')]
284
  if not score_files:
285
- print("❌ No Strict Logic signals found. Try changing coins or extending days.")
286
  return None
287
 
288
  print(f"\n🧩 [Phase 2] Running Strict Logic Simulation...")
@@ -317,11 +338,21 @@ class HeavyDutyBacktester:
317
 
318
  best = sorted(final_results, key=lambda x: x['final_balance'], reverse=True)[0]
319
 
 
320
  print("\n" + "="*60)
321
  print(f"🏆 CHAMPION STRICT REPORT ({self.BACKTEST_DAYS} Days):")
322
- print(f" 💰 Final Balance: ${best['final_balance']:,.2f}")
323
- print(f" 🚀 Net PnL: ${best['net_profit']:,.2f}")
324
- print(f" 📊 Trades: {best['total_trades']}")
 
 
 
 
 
 
 
 
 
325
  print("-" * 60)
326
  print(f" ⚙️ Config: Titan={best['config']['w_titan']} | Struct={best['config']['w_struct']} | Thresh={best['config']['thresh']}")
327
  print("="*60)
 
1
  # ============================================================
2
+ # 🧪 backtest_engine.py (V69.0 - GEM-Architect: Detailed Analytics)
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 = 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()
 
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)
 
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:
 
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
  })
147
 
148
  if ai_results:
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
  "balance": initial_capital,
177
  "allocated": 0.0,
178
  "positions": {},
179
+ "trades_history": [] # Store {pnl: float}
180
  }
181
 
182
  w_titan = config['w_titan']
183
+ w_struct = config['w_struct']
184
  entry_thresh = config['thresh']
185
 
186
  for ts, group in grouped_by_time:
 
204
  del wallet["positions"][sym]
205
  wallet["trades_history"].append({'pnl': net_pnl})
206
 
207
+ # 2. Entry Logic
208
  if len(wallet["positions"]) < max_slots:
209
  free_capital = wallet["balance"] - wallet["allocated"]
210
  slots_left = max_slots - len(wallet["positions"])
 
218
  sym = row['symbol']
219
  if sym in wallet["positions"]: continue
220
 
221
+ sig_type = row['signal_type']
222
+ l1_raw_score = row['l1_score']
 
223
  real_titan = row['real_titan']
224
 
 
 
 
 
225
  norm_struct = 0.0
226
+ if sig_type == 'BREAKOUT': norm_struct = min(1.0, l1_raw_score / 5.0)
227
+ elif sig_type == 'REVERSAL': norm_struct = l1_raw_score / 100.0
 
 
 
228
 
229
  score = 0.0
230
  if (w_titan + w_struct) > 0:
 
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]
249
+
250
+ win_count = len(wins)
251
+ loss_count = len(losses)
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:
267
+ current_win_streak += 1
268
+ current_loss_streak = 0
269
+ if current_win_streak > max_win_streak: max_win_streak = current_win_streak
270
+ else:
271
+ current_loss_streak += 1
272
+ current_win_streak = 0
273
+ if current_loss_streak > max_loss_streak: max_loss_streak = current_loss_streak
274
+
275
  results.append({
276
  'config': config,
277
  'final_balance': wallet["balance"],
278
  'net_profit': net_profit,
279
+ 'total_trades': total_trades,
280
+ 'win_count': win_count,
281
+ 'loss_count': loss_count,
282
+ 'win_rate': win_rate,
283
+ 'max_single_win': max_single_win,
284
+ 'max_single_loss': max_single_loss,
285
+ 'max_win_streak': max_win_streak,
286
+ 'max_loss_streak': max_loss_streak
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
300
 
 
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...")
 
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)
347
+ print(f" 📊 Total Trades: {best['total_trades']}")
348
+ print(f" ✅ Winning Trades: {best['win_count']}")
349
+ print(f" ❌ Losing Trades: {best['loss_count']}")
350
+ print(f" 📈 Win Rate: {best['win_rate']:.1f}%")
351
+ print("-" * 60)
352
+ print(f" 🟢 Max Single Win: ${best['max_single_win']:.2f}")
353
+ print(f" 🔴 Max Single Loss: ${best['max_single_loss']:.2f}")
354
+ print(f" 🔥 Max Win Streak: {best['max_win_streak']} trades")
355
+ print(f" 🧊 Max Loss Streak: {best['max_loss_streak']} trades")
356
  print("-" * 60)
357
  print(f" ⚙️ Config: Titan={best['config']['w_titan']} | Struct={best['config']['w_struct']} | Thresh={best['config']['thresh']}")
358
  print("="*60)