Riy777 commited on
Commit
22184d7
·
1 Parent(s): 11e97ef

Update ml_engine/monte_carlo.py

Browse files
Files changed (1) hide show
  1. ml_engine/monte_carlo.py +78 -64
ml_engine/monte_carlo.py CHANGED
@@ -3,15 +3,39 @@ import numpy as np
3
  import pandas as pd
4
  from arch import arch_model
5
  import lightgbm as lgb
6
- from sklearn.preprocessing import StandardScaler
7
- # نستورد مكتبات المؤشرات (نفترض وجودها في ملف المؤشرات أو نحسبها يدوياً)
8
- # لتبسيط الأمر هنا، سنستخدم pandas_ta إذا كانت متاحة، أو حسابات بسيطة
 
9
  try:
10
  import pandas_ta as ta
11
  except ImportError:
12
  print("⚠️ مكتبة pandas_ta غير موجودة، سيتم استخدام حسابات يدوية للمؤشرات.")
13
  ta = None
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  class MonteCarloAnalyzer:
16
  def __init__(self):
17
  self.simulation_results = {}
@@ -19,13 +43,9 @@ class MonteCarloAnalyzer:
19
  async def generate_1h_price_distribution(self, ohlcv_data, target_profit_percent=0.005):
20
  """
21
  (المرحلة 1 - سريعة)
22
- محاكاة مونت كارلو لتوليد توزيع سعري للساعة القادمة (للفرز الأولي).
23
- - تستخدم توزيع Student-t (للذيول الثقيلة).
24
- - تستخدم نموذج Merton Jump-Diffusion (للقفزات السعرية).
25
- - تستخدم المتوسط/الانحراف التاريخي البسيط.
26
  """
27
  try:
28
- # 1. التحقق من جودة البيانات
29
  if not ohlcv_data or '1h' not in ohlcv_data or len(ohlcv_data['1h']) < 30:
30
  if '15m' in ohlcv_data and len(ohlcv_data['15m']) >= 50:
31
  closes = np.array([candle[4] for candle in ohlcv_data['15m']])
@@ -47,26 +67,25 @@ class MonteCarloAnalyzer:
47
  self.simulation_results = {'error': 'Invalid current price <= 0'}
48
  return None
49
 
50
- # 2. حساب الإحصائيات الأساسية (العوائد اللوغاريتمية)
51
  log_returns = np.log(closes[1:] / closes[:-1])
52
- log_returns = log_returns[~np.isnan(log_returns) & ~np.isinf(log_returns)] # تنظيف
53
 
54
  if len(log_returns) < 20:
55
  self.simulation_results = {'error': 'Insufficient log returns (< 20)'}
56
  return None
57
 
58
- # 🔴 استخدام المتوسط والانحراف التاريخي (بسيط وسريع)
59
  mean_return = np.mean(log_returns)
60
  std_return = np.std(log_returns)
61
 
62
- # 3. إعداد باراميترات المحاكاة
63
- num_simulations = 5000 # عدد المسارات
64
- t_df = 10 # درجات الحرية (DOF) لتوزيع Student-t
65
- jump_lambda = 0.05 # احتمالية حدوث قفزة في الساعة (5%)
66
- jump_mean = 0.0 # متوسط حجم القفزة
67
- jump_std = std_return * 3.0 # تقلب القفزة
68
 
69
- # 4. تشغيل المحاكاة (كما في الإصدار السابق)
70
  drift = (mean_return - 0.5 * std_return**2)
71
  diffusion = std_return * np.random.standard_t(df=t_df, size=num_simulations)
72
  jump_mask = np.random.rand(num_simulations) < jump_lambda
@@ -76,7 +95,7 @@ class MonteCarloAnalyzer:
76
  simulated_log_returns = drift + diffusion + jump_component
77
  simulated_prices = current_price * np.exp(simulated_log_returns)
78
 
79
- # 5. حساب المخرجات والتوزيع (كما في الإصدار السابق)
80
  mean_price = np.mean(simulated_prices)
81
  median_price = np.median(simulated_prices)
82
  percentiles = np.percentile(simulated_prices, [2.5, 5, 25, 50, 75, 95, 97.5])
@@ -106,29 +125,29 @@ class MonteCarloAnalyzer:
106
  'CVaR_95_value': CVaR_95_value,
107
  },
108
  'probability_of_gain': probability_of_gain,
109
- 'raw_simulated_prices': simulated_prices[:100]
110
  }
111
- return self.simulation_results
 
 
 
 
112
 
113
  except Exception as e:
114
- self.simulation_results = {'error': f'Phase 1 MC Error: {e}'}
 
 
115
  return None
116
 
117
  # 🔴 --- دالة جديدة --- 🔴
118
  async def generate_1h_distribution_advanced(self, ohlcv_data, target_profit_percent=0.005):
119
  """
120
  (المرحلة 2+3 - متقدمة)
121
- محاكاة مونت كارلو لتوليد توزيع سعري دقيق (لأفضل 10 مرشحين).
122
- - تستخدم GARCH(1,1) لتوقع التقلب (Phase 2).
123
- - تستخدم LightGBM لتوقع الميل/Drift (Phase 3).
124
- - تستخدم Student-t و Jumps للمحاكاة.
125
  """
126
  try:
127
  # 1. إعداد البيانات (DataFrame)
128
- # نستخدم إطار 1h لأنه الأنسب لـ GARCH/LGBM لتوقع الساعة القادمة
129
  if not ohlcv_data or '1h' not in ohlcv_data or len(ohlcv_data['1h']) < 50:
130
  self.simulation_results = {'error': 'Advanced MC requires 1h data (>= 50 candles)'}
131
- # كحل احتياطي، يمكننا العودة للنموذج البسيط إذا فشل المتقدم
132
  return await self.generate_1h_price_distribution(ohlcv_data, target_profit_percent)
133
 
134
  candles = ohlcv_data['1h']
@@ -142,39 +161,37 @@ class MonteCarloAnalyzer:
142
  raise ValueError("DataFrame creation failed or insufficient data after processing")
143
 
144
  current_price = df['close'].iloc[-1]
145
-
146
- # 2. حساب العوائد اللوغاريتمية (أساس كل الحسابات)
147
  df['log_returns'] = np.log(df['close'] / df['close'].shift(1)).fillna(0)
148
  log_returns_series = df['log_returns'].replace([np.inf, -np.inf], 0)
149
 
150
  # 3. (Phase 2) توقع التقلب باستخدام GARCH(1,1)
151
  try:
152
- # نضرب العوائد في 100 لتساعد GARCH على الاستقرار (ممارسة شائعة)
153
- garch_model = arch_model(log_returns_series * 100, vol='Garch', p=1, q=1, dist='t')
154
- # 🔴 استخدام disp='off' لإيقاف الطباعة الكثيفة، كما طلبت
155
  res = garch_model.fit(update_freq=0, disp='off')
156
  forecast = res.forecast(horizon=1)
157
- # أخذ التباين المتوقع (variance) وقسمته على 10000 (لأننا ضربنا في 100)
158
- forecasted_var = forecast.variance.iloc[-1, 0] / 10000
159
  forecasted_std_return = np.sqrt(forecasted_var)
 
160
  except Exception as garch_err:
161
- # في حال فشل GARCH (بيانات غير مستقرة)، نعود للانحراف المعياري العادي
162
- forecasted_std_return = np.std(log_returns_series.iloc[-30:]) # انحراف آخر 30 شمعة
163
  print(f"⚠️ GARCH failed, using std: {garch_err}")
164
 
165
 
166
  # 4. (Phase 3) توقع الميل (Drift) باستخدام LightGBM
167
  try:
168
- # 4a. هندسة الميزات
169
  if ta:
170
  df['rsi'] = ta.rsi(df['close'], length=14)
171
  macd = ta.macd(df['close'], fast=12, slow=26, signal=9)
172
  df['macd_hist'] = macd['MACDh_12_26_9']
173
- else: # حسابات يدوية بسيطة
174
  delta = df['close'].diff()
175
  gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
176
  loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
177
- rs = gain / loss
178
  df['rsi'] = 100 - (100 / (1 + rs))
179
  df['macd_hist'] = df['close'].ewm(span=12).mean() - df['close'].ewm(span=26).mean()
180
 
@@ -187,39 +204,32 @@ class MonteCarloAnalyzer:
187
  if df.empty or len(df) < 20:
188
  raise ValueError("Insufficient data after feature engineering")
189
 
190
- # 4b. إعداد بيانات التدريب والتنبؤ
191
- df['target'] = df['log_returns'].shift(-1) # الهدف هو العائد *التالي*
192
  df.dropna(inplace=True)
193
-
194
  X = df[features]
195
  y = df['target']
196
-
197
- X_train, y_train = X.iloc[:-1], y.iloc[:-1] # كل البيانات ما عدا الأخيرة
198
- X_predict = X.iloc[-1:] # آخر صف من الميزات للتنبؤ
199
 
200
- # 4c. تدريب نموذج LGBM
201
  lgbm_model = lgb.LGBMRegressor(n_estimators=100, learning_rate=0.1, n_jobs=1, verbose=-1)
202
  lgbm_model.fit(X_train, y_train)
203
 
204
- # 4d. التنبؤ بالميل
205
  forecasted_mean_return = lgbm_model.predict(X_predict)[0]
206
 
207
  except Exception as lgbm_err:
208
- # في حال فشل LGBM، نعود للمتوسط العادي
209
- forecasted_mean_return = np.mean(log_returns_series.iloc[-30:]) # متوسط آخر 30 شمعة
210
  print(f"⚠️ LGBM failed, using mean: {lgbm_err}")
211
 
212
- # 5. تشغيل المحاكاة بالقيم الديناميكية
213
-
214
- # استخدام نفس الباراميترات (T-Dist, Jumps)
215
  num_simulations = 5000
216
  t_df = 10
217
  jump_lambda = 0.05
218
  jump_mean = 0.0
219
- # تقلب القفزة يعتمد الآن على التقلب المتوقع من GARCH
220
  jump_std = forecasted_std_return * 3.0
221
 
222
- # 🔴 استخدام القيم المتوقعة
223
  mean_return = forecasted_mean_return
224
  std_return = forecasted_std_return
225
 
@@ -233,7 +243,7 @@ class MonteCarloAnalyzer:
233
  simulated_log_returns = drift + diffusion + jump_component
234
  simulated_prices = current_price * np.exp(simulated_log_returns)
235
 
236
- # 6. حساب المخرجات والتوزيع (نفس منطق المرحلة 1)
237
  mean_price = np.mean(simulated_prices)
238
  median_price = np.median(simulated_prices)
239
  percentiles = np.percentile(simulated_prices, [2.5, 5, 25, 50, 75, 95, 97.5])
@@ -249,11 +259,11 @@ class MonteCarloAnalyzer:
249
  probability_of_gain = np.mean(simulated_prices >= target_price)
250
 
251
  self.simulation_results = {
252
- 'simulation_model': 'Phase2_GARCH_LGBM', # 🔴 تحديد النموذج المتقدم
253
  'num_simulations': num_simulations,
254
  'current_price': current_price,
255
- 'forecasted_drift_lgbm': forecasted_mean_return, # 🔴 إضافة للتتبع
256
- 'forecasted_vol_garch': forecasted_std_return, # 🔴 إضافة للتتبع
257
  'distribution_summary': {'mean_price': mean_price, 'median_price': median_price},
258
  'prediction_interval_50': pi_50,
259
  'prediction_interval_90': pi_90,
@@ -265,15 +275,19 @@ class MonteCarloAnalyzer:
265
  'CVaR_95_value': CVaR_95_value,
266
  },
267
  'probability_of_gain': probability_of_gain,
268
- 'raw_simulated_prices': simulated_prices[:100]
269
  }
270
- return self.simulation_results
 
 
 
 
271
 
272
  except Exception as e:
273
  print(f"❌ خطأ فادح في محاكاة مونت كارلو المتقدمة (GARCH/LGBM): {e}")
274
  traceback.print_exc()
275
- self.simulation_results = {'error': f'Advanced MC Error: {e}'}
276
- # العودة إلى المرحلة 1 في حالة الفشل الفادح
277
  return await self.generate_1h_price_distribution(ohlcv_data, target_profit_percent)
278
 
279
  def _calculate_trend_adjustment(self, closes):
@@ -288,4 +302,4 @@ class MonteCarloAnalyzer:
288
  else: return 1.0
289
  except Exception: return 1.0
290
 
291
- print("✅ ML Module: Advanced Monte Carlo Analyzer loaded (Phase 1 + Phase 2/3)")
 
3
  import pandas as pd
4
  from arch import arch_model
5
  import lightgbm as lgb
6
+ import traceback # (Import traceback)
7
+ import json # (Import json for sanitizing)
8
+
9
+ # (Import pandas_ta or set to None)
10
  try:
11
  import pandas_ta as ta
12
  except ImportError:
13
  print("⚠️ مكتبة pandas_ta غير موجودة، سيتم استخدام حسابات يدوية للمؤشرات.")
14
  ta = None
15
 
16
+ # 🔴 --- START OF CHANGE --- 🔴
17
+ # (New Helper function to fix JSON serialization)
18
+ def _sanitize_results_for_json(results_dict):
19
+ """
20
+ Recursively converts numpy types (ndarray, np.float64, etc.)
21
+ in a dictionary to standard Python types (list, float)
22
+ to make it JSON serializable.
23
+ """
24
+ if isinstance(results_dict, dict):
25
+ return {k: _sanitize_results_for_json(v) for k, v in results_dict.items()}
26
+ elif isinstance(results_dict, list):
27
+ return [_sanitize_results_for_json(v) for v in results_dict]
28
+ elif isinstance(results_dict, np.ndarray):
29
+ return results_dict.tolist() # (Fixes ndarray error)
30
+ elif isinstance(results_dict, (np.float64, np.float32, np.float_)):
31
+ return float(results_dict) # (Fixes np.float error)
32
+ elif isinstance(results_dict, (np.int64, np.int32, np.int_)):
33
+ return int(results_dict) # (Proactive fix for int types)
34
+ else:
35
+ return results_dict
36
+ # 🔴 --- END OF CHANGE --- 🔴
37
+
38
+
39
  class MonteCarloAnalyzer:
40
  def __init__(self):
41
  self.simulation_results = {}
 
43
  async def generate_1h_price_distribution(self, ohlcv_data, target_profit_percent=0.005):
44
  """
45
  (المرحلة 1 - سريعة)
 
 
 
 
46
  """
47
  try:
48
+ # (Data quality checks - unchanged)
49
  if not ohlcv_data or '1h' not in ohlcv_data or len(ohlcv_data['1h']) < 30:
50
  if '15m' in ohlcv_data and len(ohlcv_data['15m']) >= 50:
51
  closes = np.array([candle[4] for candle in ohlcv_data['15m']])
 
67
  self.simulation_results = {'error': 'Invalid current price <= 0'}
68
  return None
69
 
70
+ # (Statistical calculation - unchanged)
71
  log_returns = np.log(closes[1:] / closes[:-1])
72
+ log_returns = log_returns[~np.isnan(log_returns) & ~np.isinf(log_returns)]
73
 
74
  if len(log_returns) < 20:
75
  self.simulation_results = {'error': 'Insufficient log returns (< 20)'}
76
  return None
77
 
 
78
  mean_return = np.mean(log_returns)
79
  std_return = np.std(log_returns)
80
 
81
+ # (Simulation parameters - unchanged)
82
+ num_simulations = 5000
83
+ t_df = 10
84
+ jump_lambda = 0.05
85
+ jump_mean = 0.0
86
+ jump_std = std_return * 3.0
87
 
88
+ # (Simulation run - unchanged)
89
  drift = (mean_return - 0.5 * std_return**2)
90
  diffusion = std_return * np.random.standard_t(df=t_df, size=num_simulations)
91
  jump_mask = np.random.rand(num_simulations) < jump_lambda
 
95
  simulated_log_returns = drift + diffusion + jump_component
96
  simulated_prices = current_price * np.exp(simulated_log_returns)
97
 
98
+ # (Output calculation - unchanged)
99
  mean_price = np.mean(simulated_prices)
100
  median_price = np.median(simulated_prices)
101
  percentiles = np.percentile(simulated_prices, [2.5, 5, 25, 50, 75, 95, 97.5])
 
125
  'CVaR_95_value': CVaR_95_value,
126
  },
127
  'probability_of_gain': probability_of_gain,
128
+ 'raw_simulated_prices': simulated_prices[:100] # (This is the ndarray)
129
  }
130
+
131
+ # 🔴 --- START OF CHANGE --- 🔴
132
+ # (Sanitize the results before returning)
133
+ return _sanitize_results_for_json(self.simulation_results)
134
+ # 🔴 --- END OF CHANGE --- 🔴
135
 
136
  except Exception as e:
137
+ print(f"❌ خطأ فادح في محاكاة مونت كارلو (Phase 1): {e}")
138
+ traceback.print_exc()
139
+ self.simulation_results = {'error': f'Phase 1 MC Error: {str(e)}'}
140
  return None
141
 
142
  # 🔴 --- دالة جديدة --- 🔴
143
  async def generate_1h_distribution_advanced(self, ohlcv_data, target_profit_percent=0.005):
144
  """
145
  (المرحلة 2+3 - متقدمة)
 
 
 
 
146
  """
147
  try:
148
  # 1. إعداد البيانات (DataFrame)
 
149
  if not ohlcv_data or '1h' not in ohlcv_data or len(ohlcv_data['1h']) < 50:
150
  self.simulation_results = {'error': 'Advanced MC requires 1h data (>= 50 candles)'}
 
151
  return await self.generate_1h_price_distribution(ohlcv_data, target_profit_percent)
152
 
153
  candles = ohlcv_data['1h']
 
161
  raise ValueError("DataFrame creation failed or insufficient data after processing")
162
 
163
  current_price = df['close'].iloc[-1]
 
 
164
  df['log_returns'] = np.log(df['close'] / df['close'].shift(1)).fillna(0)
165
  log_returns_series = df['log_returns'].replace([np.inf, -np.inf], 0)
166
 
167
  # 3. (Phase 2) توقع التقلب باستخدام GARCH(1,1)
168
  try:
169
+ # 🔴 --- START OF CHANGE --- 🔴
170
+ # (Fix DataScaleWarning: Rescale by 10000 instead of 100)
171
+ garch_model = arch_model(log_returns_series * 10000, vol='Garch', p=1, q=1, dist='t')
172
  res = garch_model.fit(update_freq=0, disp='off')
173
  forecast = res.forecast(horizon=1)
174
+ # (Divide by 10000^2)
175
+ forecasted_var = forecast.variance.iloc[-1, 0] / (10000**2)
176
  forecasted_std_return = np.sqrt(forecasted_var)
177
+ # 🔴 --- END OF CHANGE --- 🔴
178
  except Exception as garch_err:
179
+ forecasted_std_return = np.std(log_returns_series.iloc[-30:])
 
180
  print(f"⚠️ GARCH failed, using std: {garch_err}")
181
 
182
 
183
  # 4. (Phase 3) توقع الميل (Drift) باستخدام LightGBM
184
  try:
185
+ # 4a. هندسة الميزات (Unchanged)
186
  if ta:
187
  df['rsi'] = ta.rsi(df['close'], length=14)
188
  macd = ta.macd(df['close'], fast=12, slow=26, signal=9)
189
  df['macd_hist'] = macd['MACDh_12_26_9']
190
+ else:
191
  delta = df['close'].diff()
192
  gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
193
  loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
194
+ rs = gain / (loss + 1e-9) # (Added 1e-9 to prevent zero division)
195
  df['rsi'] = 100 - (100 / (1 + rs))
196
  df['macd_hist'] = df['close'].ewm(span=12).mean() - df['close'].ewm(span=26).mean()
197
 
 
204
  if df.empty or len(df) < 20:
205
  raise ValueError("Insufficient data after feature engineering")
206
 
207
+ # 4b. إعداد بيانات التدريب والتنبؤ (Unchanged)
208
+ df['target'] = df['log_returns'].shift(-1)
209
  df.dropna(inplace=True)
 
210
  X = df[features]
211
  y = df['target']
212
+ X_train, y_train = X.iloc[:-1], y.iloc[:-1]
213
+ X_predict = X.iloc[-1:]
 
214
 
215
+ # 4c. تدريب نموذج LGBM (Unchanged)
216
  lgbm_model = lgb.LGBMRegressor(n_estimators=100, learning_rate=0.1, n_jobs=1, verbose=-1)
217
  lgbm_model.fit(X_train, y_train)
218
 
219
+ # 4d. التنبؤ بالميل (Unchanged)
220
  forecasted_mean_return = lgbm_model.predict(X_predict)[0]
221
 
222
  except Exception as lgbm_err:
223
+ forecasted_mean_return = np.mean(log_returns_series.iloc[-30:])
 
224
  print(f"⚠️ LGBM failed, using mean: {lgbm_err}")
225
 
226
+ # 5. تشغيل المحاكاة بالقيم الديناميكية (Unchanged)
 
 
227
  num_simulations = 5000
228
  t_df = 10
229
  jump_lambda = 0.05
230
  jump_mean = 0.0
 
231
  jump_std = forecasted_std_return * 3.0
232
 
 
233
  mean_return = forecasted_mean_return
234
  std_return = forecasted_std_return
235
 
 
243
  simulated_log_returns = drift + diffusion + jump_component
244
  simulated_prices = current_price * np.exp(simulated_log_returns)
245
 
246
+ # 6. حساب المخرجات والتوزيع (Unchanged)
247
  mean_price = np.mean(simulated_prices)
248
  median_price = np.median(simulated_prices)
249
  percentiles = np.percentile(simulated_prices, [2.5, 5, 25, 50, 75, 95, 97.5])
 
259
  probability_of_gain = np.mean(simulated_prices >= target_price)
260
 
261
  self.simulation_results = {
262
+ 'simulation_model': 'Phase2_GARCH_LGBM',
263
  'num_simulations': num_simulations,
264
  'current_price': current_price,
265
+ 'forecasted_drift_lgbm': forecasted_mean_return,
266
+ 'forecasted_vol_garch': forecasted_std_return,
267
  'distribution_summary': {'mean_price': mean_price, 'median_price': median_price},
268
  'prediction_interval_50': pi_50,
269
  'prediction_interval_90': pi_90,
 
275
  'CVaR_95_value': CVaR_95_value,
276
  },
277
  'probability_of_gain': probability_of_gain,
278
+ 'raw_simulated_prices': simulated_prices[:100] # (This is the ndarray)
279
  }
280
+
281
+ # 🔴 --- START OF CHANGE --- 🔴
282
+ # (Sanitize the results before returning)
283
+ return _sanitize_results_for_json(self.simulation_results)
284
+ # 🔴 --- END OF CHANGE --- 🔴
285
 
286
  except Exception as e:
287
  print(f"❌ خطأ فادح في محاكاة مونت كارلو المتقدمة (GARCH/LGBM): {e}")
288
  traceback.print_exc()
289
+ self.simulation_results = {'error': f'Advanced MC Error: {str(e)}'}
290
+ # (Fall back to Phase 1, which also sanitizes its output now)
291
  return await self.generate_1h_price_distribution(ohlcv_data, target_profit_percent)
292
 
293
  def _calculate_trend_adjustment(self, closes):
 
302
  else: return 1.0
303
  except Exception: return 1.0
304
 
305
+ print("✅ ML Module: Advanced Monte Carlo Analyzer loaded (FIXED: JSON Serializable & GARCH Scale)")