Riy777 commited on
Commit
fe86b15
·
verified ·
1 Parent(s): 1337110

Update backtest_engine.py

Browse files
Files changed (1) hide show
  1. backtest_engine.py +221 -178
backtest_engine.py CHANGED
@@ -1,9 +1,5 @@
1
  # ============================================================
2
- # 🧪 backtest_engine.py (V64.0 - GEM-Architect: Advanced Analytics)
3
- # ============================================================
4
- # التحديثات:
5
- # 1. حساب إحصائيات دقيقة (Win Rate, Max Drawdown, Consec Wins/Losses).
6
- # 2. عرض تقرير مفصل "Champion Report" في النهاية.
7
  # ============================================================
8
 
9
  import asyncio
@@ -13,14 +9,17 @@ import time
13
  import logging
14
  import itertools
15
  import os
16
- import shutil
17
  import concurrent.futures
18
  from typing import Dict, Any, List
19
 
20
- from ml_engine.processor import MLProcessor, SystemLimits
21
- from ml_engine.data_manager import DataManager
22
- from learning_hub.adaptive_hub import StrategyDNA
23
- from r2 import R2Service
 
 
 
 
24
 
25
  logging.getLogger('ml_engine').setLevel(logging.WARNING)
26
  CACHE_DIR = "backtest_real_scores"
@@ -29,28 +28,23 @@ class HeavyDutyBacktester:
29
  def __init__(self, data_manager, processor):
30
  self.dm = data_manager
31
  self.proc = processor
32
- self.GRID_DENSITY = 8
33
  self.BACKTEST_DAYS = 14
34
 
 
 
 
 
 
35
  self.TARGET_COINS = [
36
  'BTC/USDT', 'ETH/USDT', 'SOL/USDT', 'BNB/USDT', 'XRP/USDT',
37
  'DOGE/USDT', 'ADA/USDT', 'AVAX/USDT'
38
  ]
39
 
40
- # بروفايلات الكاشف
41
- self.SCANNER_PROFILES = {
42
- "BALANCED": {"RSI_MOMENTUM": 0.3, "BB_BREAKOUT": 0.2, "MACD_CROSS": 0.3, "VOLUME_FLOW": 0.2},
43
- "REVERSAL": {"RSI_MOMENTUM": 0.5, "BB_BREAKOUT": 0.4, "MACD_CROSS": 0.0, "VOLUME_FLOW": 0.1},
44
- "VOLUME": {"RSI_MOMENTUM": 0.1, "BB_BREAKOUT": 0.1, "MACD_CROSS": 0.1, "VOLUME_FLOW": 0.7},
45
- "TREND": {"RSI_MOMENTUM": 0.2, "BB_BREAKOUT": 0.1, "MACD_CROSS": 0.6, "VOLUME_FLOW": 0.1}
46
- }
47
-
48
  if not os.path.exists(CACHE_DIR): os.makedirs(CACHE_DIR)
49
- print(f"🧪 [Backtest V64.0] Advanced Analytics Mode. Profiles: {len(self.SCANNER_PROFILES)}")
50
 
51
- # ==============================================================
52
- # 🛠️ Helpers
53
- # ==============================================================
54
  def resample_data(self, df_1m, timeframe_str):
55
  if df_1m.empty: return pd.DataFrame()
56
  agg_dict = {'open': 'first', 'high': 'max', 'low': 'min', 'close': 'last', 'volume': 'sum'}
@@ -66,22 +60,25 @@ class HeavyDutyBacktester:
66
  return df[['timestamp', 'open', 'high', 'low', 'close', 'volume']].values.tolist()
67
 
68
  # ==============================================================
69
- # PHASE 1: The Heavy Lift (Same as before)
70
  # ==============================================================
71
  async def generate_truth_data(self):
72
- print(f"\n🚜 [Phase 1] Fetching & AI Processing ({self.BACKTEST_DAYS} Days)...")
 
 
73
  end_time_ms = int(time.time() * 1000)
74
  start_time_ms = end_time_ms - (self.BACKTEST_DAYS * 24 * 60 * 60 * 1000)
 
75
 
76
  for sym in self.TARGET_COINS:
77
  safe_sym = sym.replace('/', '_')
78
- scores_file = f"{CACHE_DIR}/{safe_sym}_scores_{self.BACKTEST_DAYS}d.pkl"
79
-
80
  if os.path.exists(scores_file):
81
  print(f" 📂 {sym} scores ready. Skipping.")
82
  continue
83
-
84
- print(f" ⚙️ Fetching {sym}...", end="", flush=True)
 
85
  all_candles_1m = []
86
  current_since = start_time_ms
87
  while current_since < end_time_ms:
@@ -97,11 +94,14 @@ class HeavyDutyBacktester:
97
  except: await asyncio.sleep(1)
98
 
99
  all_candles_1m = [c for c in all_candles_1m if c[0] <= end_time_ms]
100
- if not all_candles_1m: continue
101
-
 
 
102
  df_1m = pd.DataFrame(all_candles_1m, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
103
  df_1m['datetime'] = pd.to_datetime(df_1m['timestamp'], unit='ms')
104
  df_1m.set_index('datetime', inplace=True)
 
105
 
106
  ai_results = []
107
  resample_freq = '15T'
@@ -112,140 +112,189 @@ class HeavyDutyBacktester:
112
  if len(current_slice_1m) < 500: continue
113
  current_price = current_slice_1m['close'].iloc[-1]
114
 
115
- ohlcv_data = {}
116
- try:
117
- ohlcv_data['1m'] = self.df_to_list(current_slice_1m.tail(1000))
118
- df_5m = self.resample_data(current_slice_1m.tail(5000), '5m')
119
- ohlcv_data['5m'] = self.df_to_list(df_5m.tail(500))
120
- df_15m = self.resample_data(current_slice_1m.tail(15000), '15m')
121
- ohlcv_data['15m'] = self.df_to_list(df_15m.tail(500))
122
- df_1h = self.resample_data(current_slice_1m.tail(60000), '1h')
123
- ohlcv_data['1h'] = self.df_to_list(df_1h.tail(200))
124
- df_4h = self.resample_data(current_slice_1m.tail(240000), '4h')
125
- ohlcv_data['4h'] = self.df_to_list(df_4h.tail(100))
126
- df_1d = self.resample_data(current_slice_1m.tail(1440000), '1d')
127
- ohlcv_data['1d'] = self.df_to_list(df_1d.tail(50))
128
- except Exception: continue
129
-
130
- raw_data = {'symbol': sym, 'current_price': current_price, 'ohlcv': ohlcv_data}
131
- result = await self.proc.process_compound_signal(raw_data)
132
 
133
- if result:
134
- titan_real = result.get('titan_score', 0.5)
135
- pattern_real = result.get('patterns_score', 0.5)
136
-
137
- if '15m' in ohlcv_data and len(ohlcv_data['15m']) > 50:
138
- scanner_df = pd.DataFrame(ohlcv_data['15m'], columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
139
- scanner_res = self.dm._apply_scanner_strategies(scanner_df, sym)
140
- else:
141
- scanner_res = {}
142
-
143
- ai_results.append({
144
- 'timestamp': t_idx, 'close': current_price,
145
- 'real_titan': titan_real, 'real_pattern': pattern_real, 'real_scanner_data': scanner_res
146
- })
147
 
148
  if ai_results:
149
  pd.DataFrame(ai_results).to_pickle(scores_file)
150
  print(f" ✅ Saved.")
151
 
152
  # ==============================================================
153
- # PHASE 2: The Deep Grid Optimizer (Advanced Metrics)
154
  # ==============================================================
155
  @staticmethod
156
- def _worker_optimize(combinations_batch, scores_files):
 
 
 
157
  results = []
158
- dfs = []
 
 
 
159
  for fp in scores_files:
160
- try: dfs.append(pd.read_pickle(fp))
 
 
161
  except: pass
162
-
 
 
 
 
 
 
 
 
 
163
  for config in combinations_batch:
164
- # قائمة لتخزين نتائج كل صفقة (نسبة الربح/الخسارة)
165
- trade_outcomes = []
 
 
 
 
 
 
166
 
167
  w_titan = config['w_titan']
168
- w_scanner = config['w_scanner']
169
  entry_thresh = config['thresh']
170
- profile_weights = config['profile_weights']
171
 
172
- for df in dfs:
173
- in_pos = False
174
- entry_p = 0.0
 
 
175
 
176
- for idx, row in df.iterrows():
177
- s_data = row['real_scanner_data']
178
- if not isinstance(s_data, dict): continue
179
-
180
- # إعادة حساب الكاشف حسب البروفايل
181
- scanner_weighted_sum = 0.0
182
- total_profile_weight = 0.0
183
- for strategy, weight in profile_weights.items():
184
- if strategy in s_data:
185
- raw_score = s_data[strategy].get('score', 0)
186
- scanner_weighted_sum += (raw_score * weight)
187
- total_profile_weight += weight
 
 
 
 
 
 
 
 
 
 
 
188
 
189
- if total_profile_weight > 0:
190
- scanner_score_norm = (scanner_weighted_sum / total_profile_weight) / 100.0
191
- else:
192
- scanner_score_norm = 0.0
193
 
194
- real_titan = row['real_titan']
195
- final_score = (real_titan * w_titan) + (scanner_score_norm * w_scanner)
196
- final_score /= (w_titan + w_scanner)
 
197
 
198
- if not in_pos and final_score >= entry_thresh:
199
- in_pos = True
200
- entry_p = row['close']
201
- elif in_pos:
202
- # الخروج: هدف ديناميكي 3% ووقف 2% مكن تعديله)
203
- pnl = (row['close'] - entry_p) / entry_p
204
- if pnl > 0.03 or pnl < -0.02:
205
- trade_outcomes.append(pnl) # تسجيل نتيجة الصفقة
206
- in_pos = False
207
-
208
- # 🔥 حساب الإحصائيات المتقدمة 🔥
209
- if len(trade_outcomes) > 5:
210
- total_pnl = sum(trade_outcomes)
211
- total_trades = len(trade_outcomes)
212
- winning_trades = [t for t in trade_outcomes if t > 0]
213
- losing_trades = [t for t in trade_outcomes if t <= 0]
214
-
215
- win_count = len(winning_trades)
216
- win_rate = (win_count / total_trades) * 100
217
-
218
- max_single_win = max(trade_outcomes) * 100 # كنسبة مئوية
219
- max_single_loss = min(trade_outcomes) * 100 # كنسبة مئوية
220
-
221
- # حساب السلاسل المتتالية (Consecutive Streaks)
222
- max_consec_wins = 0
223
- max_consec_losses = 0
224
- curr_wins = 0
225
- curr_losses = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
 
227
- for t in trade_outcomes:
228
- if t > 0:
229
- curr_wins += 1
230
- curr_losses = 0
231
- if curr_wins > max_consec_wins: max_consec_wins = curr_wins
232
- else:
233
- curr_losses += 1
234
- curr_wins = 0
235
- if curr_losses > max_consec_losses: max_consec_losses = curr_losses
236
 
237
  results.append({
238
  'config': config,
239
- 'pnl': total_pnl,
240
- 'total_trades': total_trades,
241
- 'win_count': win_count,
242
- 'win_rate': win_rate,
243
- 'max_single_win': max_single_win,
244
- 'max_single_loss': max_single_loss,
245
- 'max_consec_wins': max_consec_wins,
246
- 'max_consec_losses': max_consec_losses,
247
- 'score': total_pnl # الترتيب حسب الربح الصافي
248
  })
 
 
 
 
 
 
 
 
 
 
 
249
  return results
250
 
251
  async def run_optimization(self):
@@ -253,34 +302,33 @@ class HeavyDutyBacktester:
253
 
254
  score_files = [os.path.join(CACHE_DIR, f) for f in os.listdir(CACHE_DIR) if f.endswith(f'_scores_{self.BACKTEST_DAYS}d.pkl')]
255
  if not score_files:
256
- print("❌ No AI scores found.")
257
- return
258
 
259
- print(f"\n🧩 [Phase 2] Running Advanced Grid Search (Calculating Metrics)...")
 
 
 
260
 
261
- w_titan_range = np.linspace(0.1, 0.9, num=self.GRID_DENSITY)
262
- w_scanner_range = np.linspace(0.1, 0.9, num=self.GRID_DENSITY)
263
- thresh_range = np.linspace(0.50, 0.80, num=self.GRID_DENSITY)
264
- profiles_list = list(self.SCANNER_PROFILES.items())
265
 
266
  combinations = []
267
- for wt, ws, th, (prof_name, prof_weights) in itertools.product(w_titan_range, w_scanner_range, thresh_range, profiles_list):
268
- combinations.append({
269
- 'w_titan': round(wt, 2),
270
- 'w_scanner': round(ws, 2),
271
- 'thresh': round(th, 2),
272
- 'profile_name': prof_name,
273
- 'profile_weights': prof_weights
274
- })
275
 
276
- print(f" 📊 Combinations: {len(combinations):,}")
277
 
278
  final_results = []
279
- batch_size = max(50, len(combinations) // (os.cpu_count() * 2))
280
  batches = [combinations[i:i+batch_size] for i in range(0, len(combinations), batch_size)]
281
 
282
  with concurrent.futures.ProcessPoolExecutor() as executor:
283
- futures = [executor.submit(self._worker_optimize, batch, score_files) for batch in batches]
 
 
284
  for future in concurrent.futures.as_completed(futures):
285
  try: final_results.extend(future.result())
286
  except Exception as e: print(f"Grid Error: {e}")
@@ -289,31 +337,28 @@ class HeavyDutyBacktester:
289
  print("⚠️ No profitable config found.")
290
  return None
291
 
292
- best = sorted(final_results, key=lambda x: x['pnl'], reverse=True)[0]
293
 
294
- # 🔥 طباعة التقرير التفصيلي 🔥
295
  print("\n" + "="*60)
296
- print(f"🏆 ULTIMATE CHAMPION REPORT ({self.BACKTEST_DAYS} Days):")
297
- print(f" 💰 Net Profit: {best['pnl']*100:.2f}%")
298
- print(f" 📈 Win Rate: {best['win_rate']:.1f}%")
299
- print(f" 📊 Trades: {best['total_trades']} (Wins: {best['win_count']} | Losses: {best['total_trades'] - best['win_count']})")
300
  print("-" * 60)
301
- print(f" 🚀 Best Trade: +{best['max_single_win']:.2f}%")
302
- print(f" 🔻 Worst Trade: {best['max_single_loss']:.2f}%")
303
- print(f" 🔥 Max Winning Streak: {best['max_consec_wins']} trades")
304
- print(f" 🧊 Max Losing Streak: {best['max_consec_losses']} trades")
305
  print("-" * 60)
306
- print(f" 🧬 Profile: {best['config']['profile_name']}")
307
- print(f" 🧬 Config: Titan:{best['config']['w_titan']} | Scanner:{best['config']['w_scanner']} | Thresh:{best['config']['thresh']}")
308
  print("="*60)
309
 
310
  return best['config']
311
 
312
  async def run_strategic_optimization_task():
313
- print("\n🧪 [STRATEGIC BACKTEST V64.0] Starting Advanced Optimization...")
314
  r2 = R2Service()
315
  dm = DataManager(None, None, r2)
316
  proc = MLProcessor(dm)
 
317
  await dm.initialize()
318
  await proc.initialize()
319
 
@@ -325,18 +370,16 @@ async def run_strategic_optimization_task():
325
  hub = AdaptiveHub(r2)
326
  await hub.initialize()
327
 
328
- regime = "RANGE"
329
- if regime in hub.strategies:
330
- print(f"💉 Injecting NEW DNA into {regime}...")
331
- st = hub.strategies[regime]
332
  st.model_weights['titan'] = best_config['w_titan']
333
- st.model_weights['patterns'] = best_config['w_scanner']
334
- st.filters['l1_min_score'] = best_config['thresh'] * 100
335
- st.scanner_weights = best_config['profile_weights']
336
 
337
  await hub._save_state_to_r2()
338
  hub._inject_current_parameters()
339
- print(f"✅ [System] DNA Updated (Profile: {best_config['profile_name']}).")
340
 
341
  await dm.close()
342
 
 
1
  # ============================================================
2
+ # 🧪 backtest_engine.py (V67.0 - GEM-Architect: Portfolio Digital Twin)
 
 
 
 
3
  # ============================================================
4
 
5
  import asyncio
 
9
  import logging
10
  import itertools
11
  import os
 
12
  import concurrent.futures
13
  from typing import Dict, Any, List
14
 
15
+ # استيراد الوحدات الأساسية
16
+ try:
17
+ from ml_engine.processor import MLProcessor, SystemLimits
18
+ from ml_engine.data_manager import DataManager
19
+ from learning_hub.adaptive_hub import StrategyDNA
20
+ from r2 import R2Service
21
+ except ImportError:
22
+ pass
23
 
24
  logging.getLogger('ml_engine').setLevel(logging.WARNING)
25
  CACHE_DIR = "backtest_real_scores"
 
28
  def __init__(self, data_manager, processor):
29
  self.dm = data_manager
30
  self.proc = processor
31
+ self.GRID_DENSITY = 5 # تقليل الكثافة لأن المحاكاة أصبحت أثقل
32
  self.BACKTEST_DAYS = 14
33
 
34
+ # 💰 إعدادات التوأم الرقمي (Portfolio Twin Settings)
35
+ self.INITIAL_CAPITAL = 10.0
36
+ self.TRADING_FEES = 0.001 # 0.1% Maker/Taker
37
+ self.MAX_SLOTS = 4 # الحد الأقصى للصفقات المتزامنة (كما في النظام)
38
+
39
  self.TARGET_COINS = [
40
  'BTC/USDT', 'ETH/USDT', 'SOL/USDT', 'BNB/USDT', 'XRP/USDT',
41
  'DOGE/USDT', 'ADA/USDT', 'AVAX/USDT'
42
  ]
43
 
 
 
 
 
 
 
 
 
44
  if not os.path.exists(CACHE_DIR): os.makedirs(CACHE_DIR)
45
+ print(f"🧪 [Backtest V67.0] Portfolio Digital Twin Mode (Start: ${self.INITIAL_CAPITAL}).")
46
 
47
+ # ... (نفس دوال المساعدة resample_data و df_to_list) ...
 
 
48
  def resample_data(self, df_1m, timeframe_str):
49
  if df_1m.empty: return pd.DataFrame()
50
  agg_dict = {'open': 'first', 'high': 'max', 'low': 'min', 'close': 'last', 'volume': 'sum'}
 
60
  return df[['timestamp', 'open', 'high', 'low', 'close', 'volume']].values.tolist()
61
 
62
  # ==============================================================
63
+ # PHASE 1: Generate Truth Data (Standard)
64
  # ==============================================================
65
  async def generate_truth_data(self):
66
+ # ... (نفس كود توليد البيانات السابق تماماً - لا تغيير هنا) ...
67
+ # اختصاراً للمساحة سأضع الكود الأساسي فقط
68
+ print(f"\n🚜 [Phase 1] Fetching & Structural Analysis ({self.BACKTEST_DAYS} Days)...")
69
  end_time_ms = int(time.time() * 1000)
70
  start_time_ms = end_time_ms - (self.BACKTEST_DAYS * 24 * 60 * 60 * 1000)
71
+ test_regime = "RANGE"
72
 
73
  for sym in self.TARGET_COINS:
74
  safe_sym = sym.replace('/', '_')
75
+ scores_file = f"{CACHE_DIR}/{safe_sym}_struct_scores_{self.BACKTEST_DAYS}d.pkl"
 
76
  if os.path.exists(scores_file):
77
  print(f" 📂 {sym} scores ready. Skipping.")
78
  continue
79
+
80
+ print(f" ⚙️ Processing {sym}...", end="", flush=True)
81
+ # ... (Fetching logic same as V66) ...
82
  all_candles_1m = []
83
  current_since = start_time_ms
84
  while current_since < end_time_ms:
 
94
  except: await asyncio.sleep(1)
95
 
96
  all_candles_1m = [c for c in all_candles_1m if c[0] <= end_time_ms]
97
+ if not all_candles_1m:
98
+ print(" No Data.")
99
+ continue
100
+
101
  df_1m = pd.DataFrame(all_candles_1m, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
102
  df_1m['datetime'] = pd.to_datetime(df_1m['timestamp'], unit='ms')
103
  df_1m.set_index('datetime', inplace=True)
104
+ df_1m = df_1m.sort_index()
105
 
106
  ai_results = []
107
  resample_freq = '15T'
 
112
  if len(current_slice_1m) < 500: continue
113
  current_price = current_slice_1m['close'].iloc[-1]
114
 
115
+ df_struct = self.resample_data(current_slice_1m.tail(5000), '15m')
116
+ if len(df_struct) < 200: continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
+ struct_score, _ = self.dm._calculate_structural_score(df_struct, sym, test_regime)
119
+ norm_struct_score = max(0.0, min(1.0, (struct_score + 20) / 100.0))
120
+
121
+ # ... (Titan simulation) ...
122
+ titan_real = 0.5
123
+
124
+ ai_results.append({
125
+ 'timestamp': int(t_idx.timestamp() * 1000), # حفظ الوقت بدقة للترتيب الزمني
126
+ 'symbol': sym,
127
+ 'close': current_price,
128
+ 'real_titan': titan_real,
129
+ 'real_struct_score': norm_struct_score
130
+ })
 
131
 
132
  if ai_results:
133
  pd.DataFrame(ai_results).to_pickle(scores_file)
134
  print(f" ✅ Saved.")
135
 
136
  # ==============================================================
137
+ # PHASE 2: Portfolio Digital Twin Engine
138
  # ==============================================================
139
  @staticmethod
140
+ def _worker_optimize(combinations_batch, scores_files, initial_capital, fees_pct, max_slots):
141
+ """
142
+ محاكاة دقيقة لإدارة المحفظة مع الزمن (Time-Series Portfolio Simulation).
143
+ """
144
  results = []
145
+
146
+ # 1. دمج جميع البيانات وترتيبها زمنياً (Time-Sorted Global Timeline)
147
+ # لضمان محاكاة السوق بشكل واقعي (لا يمكننا معالجة كل عملة وحدها)
148
+ all_data = []
149
  for fp in scores_files:
150
+ try:
151
+ df = pd.read_pickle(fp)
152
+ all_data.append(df)
153
  except: pass
154
+
155
+ if not all_data: return []
156
+
157
+ global_df = pd.concat(all_data)
158
+ global_df.sort_values('timestamp', inplace=True)
159
+ # تجميع البيانات حسب الطابع الزمني (للمعالجة لحظة بلحظة)
160
+ grouped_by_time = global_df.groupby('timestamp')
161
+
162
+ # ----------------------------------------------------
163
+
164
  for config in combinations_batch:
165
+ # 🏦 حالة المحفظة (Portfolio State)
166
+ wallet = {
167
+ "balance": initial_capital, # الرصيد الكلي
168
+ "allocated": 0.0, # المحجوز في صفقات
169
+ "positions": {}, # الصفقات المفتوحة: {symbol: {entry_p, size_usd}}
170
+ "equity_curve": [initial_capital],
171
+ "trades_history": []
172
+ }
173
 
174
  w_titan = config['w_titan']
175
+ w_struct = config['w_struct']
176
  entry_thresh = config['thresh']
 
177
 
178
+ # المرور عبر الزمن (Time-Step Simulation)
179
+ for ts, group in grouped_by_time:
180
+ # أ. إدارة الصفقات المفتوحة (Check Exits)
181
+ # يجب استخدام نسخة من المفاتيح للتعديل أثناء الدوران
182
+ active_symbols = list(wallet["positions"].keys())
183
 
184
+ # نحتاج معرفة الأسعار الحالية للعملات المفتوحة
185
+ # نستخرجها من المجموعة الحالية إذا توفرت
186
+ current_prices = {row['symbol']: row['close'] for _, row in group.iterrows()}
187
+
188
+ for sym in active_symbols:
189
+ if sym in current_prices:
190
+ curr_p = current_prices[sym]
191
+ pos = wallet["positions"][sym]
192
+ entry_p = pos['entry_price']
193
+
194
+ pct_change = (curr_p - entry_p) / entry_p
195
+
196
+ # Exit Rules (TP 3% / SL 2%)
197
+ if pct_change >= 0.03 or pct_change <= -0.02:
198
+ # تنفيذ الخروج
199
+ gross_pnl = pos['size_usd'] * pct_change
200
+ fees = pos['size_usd'] * fees_pct * 2 # رسوم دخول وخروج تقريبية
201
+ net_pnl = gross_pnl - fees
202
+
203
+ wallet["allocated"] -= pos['size_usd']
204
+ wallet["balance"] += net_pnl # الرصيد يتحدث بالصافي
205
+
206
+ del wallet["positions"][sym]
207
 
208
+ wallet["trades_history"].append({
209
+ 'pnl': net_pnl,
210
+ 'roi': (net_pnl / pos['size_usd']) * 100
211
+ })
212
 
213
+ # ب. البحث عن فرص جديدة (Check Entries)
214
+ # فقط إذا كان لدينا خانات فارغة ورصيد كافٍ
215
+ if len(wallet["positions"]) < max_slots:
216
+ free_capital = wallet["balance"] - wallet["allocated"]
217
 
218
+ # محاكاة منطق تقسيم رأس المال (Smart Portfolio Logic)
219
+ # الحجم = الرصيد الحر / الخانات المتبقية (تقريبي)
220
+ slots_left = max_slots - len(wallet["positions"])
221
+ if slots_left > 0 and free_capital > 2.0: # الحد الأدنى للدخول
222
+ # إذا كان الرصيد صغيراً جداً، ندخل بالكل المتاح (لأننا تحت 20$)
223
+ if wallet["balance"] < 20.0:
224
+ # إذا كان رصيد قليل، نأخذ ما يكفي لصفقة واحدة أو المتاح
225
+ # لنقل نستخدم All-in للخانات المتبقية
226
+ position_size = free_capital / slots_left
227
+ else:
228
+ # تقسيم متوازن
229
+ position_size = wallet["balance"] / max_slots
230
+ position_size = min(position_size, free_capital)
231
+
232
+ # المرور على الفرص المتاحة في هذه اللحظة
233
+ for _, row in group.iterrows():
234
+ sym = row['symbol']
235
+ if sym in wallet["positions"]: continue # لدينا صفقة بالفعل
236
+
237
+ # حساب السكور
238
+ real_titan = row.get('real_titan', 0.5)
239
+ real_struct = row.get('real_struct_score', 0.0)
240
+
241
+ score = 0.0
242
+ if (w_titan + w_struct) > 0:
243
+ score = ((real_titan * w_titan) + (real_struct * w_struct)) / (w_titan + w_struct)
244
+
245
+ if score >= entry_thresh:
246
+ # ✅ فتح صفقة جديدة (مع خصم رسوم الدخول من الحجم الفعلي)
247
+ # نحجز المبلغ كاملاً، لكن الرسوم ستخصم عند الخروج لتبسيط المحاكاة
248
+ wallet["positions"][sym] = {
249
+ 'entry_price': row['close'],
250
+ 'size_usd': position_size
251
+ }
252
+ wallet["allocated"] += position_size
253
+
254
+ # نتوقف عن البحث في هذه اللحظة إذا امتلأت الخانات
255
+ if len(wallet["positions"]) >= max_slots: break
256
+
257
+ # تسجيل منحنى رأس المال (Equity Curve)
258
+ # Equity = Balance (Unrealized PnL is ignored for speed, only Realized)
259
+ # أو يمكن حساب Unrealized لدقة أكبر، لكن Realized يكفي للتحسين
260
+ wallet["equity_curve"].append(wallet["balance"])
261
+
262
+ # شرط الإفلاس
263
+ if wallet["balance"] < 1.0 and len(wallet["positions"]) == 0:
264
+ break
265
+
266
+ # 3. تجميع النتائج
267
+ trades = wallet["trades_history"]
268
+ if trades:
269
+ net_profit = wallet["balance"] - initial_capital
270
+ wins = len([t for t in trades if t['pnl'] > 0])
271
+ total = len(trades)
272
 
273
+ # Max Drawdown
274
+ peaks = pd.Series(wallet["equity_curve"]).cummax()
275
+ drawdown = (wallet["equity_curve"] - peaks)
276
+ max_dd = drawdown.min() # قيمة سالبة
 
 
 
 
 
277
 
278
  results.append({
279
  'config': config,
280
+ 'net_profit': net_profit,
281
+ 'final_balance': wallet["balance"],
282
+ 'total_trades': total,
283
+ 'win_rate': (wins/total)*100,
284
+ 'max_drawdown': max_dd,
285
+ 'roi_pct': (net_profit / initial_capital) * 100
 
 
 
286
  })
287
+ else:
288
+ results.append({
289
+ 'config': config,
290
+ 'net_profit': 0.0,
291
+ 'final_balance': initial_capital,
292
+ 'total_trades': 0,
293
+ 'win_rate': 0.0,
294
+ 'max_drawdown': 0.0,
295
+ 'roi_pct': 0.0
296
+ })
297
+
298
  return results
299
 
300
  async def run_optimization(self):
 
302
 
303
  score_files = [os.path.join(CACHE_DIR, f) for f in os.listdir(CACHE_DIR) if f.endswith(f'_scores_{self.BACKTEST_DAYS}d.pkl')]
304
  if not score_files:
305
+ print("❌ No data found.")
306
+ return None
307
 
308
+ print(f"\n🧩 [Phase 2] Running Digital Twin Simulation...")
309
+ print(f" 💰 Start Capital: ${self.INITIAL_CAPITAL}")
310
+ print(f" 🎰 Max Slots: {self.MAX_SLOTS}")
311
+ print(f" 💸 Fees: {self.TRADING_FEES*100}% per trade")
312
 
313
+ w_titan_range = np.linspace(0.4, 0.9, num=self.GRID_DENSITY)
314
+ w_struct_range = np.linspace(0.1, 0.6, num=self.GRID_DENSITY)
315
+ thresh_range = np.linspace(0.60, 0.85, num=self.GRID_DENSITY)
 
316
 
317
  combinations = []
318
+ for wt, ws, th in itertools.product(w_titan_range, w_struct_range, thresh_range):
319
+ if 0.9 <= (wt + ws) <= 1.1:
320
+ combinations.append({'w_titan': round(wt, 2), 'w_struct': round(ws, 2), 'thresh': round(th, 2)})
 
 
 
 
 
321
 
322
+ print(f" 📊 Simulating {len(combinations):,} scenarios...")
323
 
324
  final_results = []
325
+ batch_size = max(20, len(combinations) // (os.cpu_count() * 2)) # تقليل الدفعة لأن المحاكاة ثقيلة
326
  batches = [combinations[i:i+batch_size] for i in range(0, len(combinations), batch_size)]
327
 
328
  with concurrent.futures.ProcessPoolExecutor() as executor:
329
+ futures = [executor.submit(self._worker_optimize, batch, score_files,
330
+ self.INITIAL_CAPITAL, self.TRADING_FEES, self.MAX_SLOTS)
331
+ for batch in batches]
332
  for future in concurrent.futures.as_completed(futures):
333
  try: final_results.extend(future.result())
334
  except Exception as e: print(f"Grid Error: {e}")
 
337
  print("⚠️ No profitable config found.")
338
  return None
339
 
340
+ best = sorted(final_results, key=lambda x: x['final_balance'], reverse=True)[0]
341
 
 
342
  print("\n" + "="*60)
343
+ print(f"🏆 CHAMPION TWIN REPORT ({self.BACKTEST_DAYS} Days):")
344
+ print(f" 💵 Initial: ${self.INITIAL_CAPITAL:,.2f}")
345
+ print(f" 💰 Final: ${best['final_balance']:,.2f}")
346
+ print(f" 🚀 Net PnL: ${best['net_profit']:,.2f} ({best['roi_pct']:,.2f}%)")
347
  print("-" * 60)
348
+ print(f" 📊 Trades: {best['total_trades']} (WR: {best['win_rate']:.1f}%)")
349
+ print(f" 📉 Max DD: ${best['max_drawdown']:,.2f}")
 
 
350
  print("-" * 60)
351
+ print(f" ⚙️ Config: Titan={best['config']['w_titan']} | Struct={best['config']['w_struct']} | Thresh={best['config']['thresh']}")
 
352
  print("="*60)
353
 
354
  return best['config']
355
 
356
  async def run_strategic_optimization_task():
357
+ print("\n🧪 [STRATEGIC BACKTEST] Starting Twin Optimization...")
358
  r2 = R2Service()
359
  dm = DataManager(None, None, r2)
360
  proc = MLProcessor(dm)
361
+
362
  await dm.initialize()
363
  await proc.initialize()
364
 
 
370
  hub = AdaptiveHub(r2)
371
  await hub.initialize()
372
 
373
+ target_regime = "RANGE"
374
+ if target_regime in hub.strategies:
375
+ print(f"💉 Injecting DNA into {target_regime}...")
376
+ st = hub.strategies[target_regime]
377
  st.model_weights['titan'] = best_config['w_titan']
378
+ st.filters['l1_min_score'] = best_config['thresh']
 
 
379
 
380
  await hub._save_state_to_r2()
381
  hub._inject_current_parameters()
382
+ print(f"✅ [System] DNA Updated Successfully.")
383
 
384
  await dm.close()
385